Merge pull request #6097 from element-hq/feature/bma/favoriteWording

Improve favorite wording and icon of room
This commit is contained in:
Benoit Marty 2026-01-28 17:16:53 +01:00 committed by GitHub
commit 81a391f184
53 changed files with 305 additions and 382 deletions

View file

@ -10,7 +10,7 @@ package io.element.android.features.home.impl
import io.element.android.libraries.matrix.api.core.SessionId import io.element.android.libraries.matrix.api.core.SessionId
sealed interface HomeEvents { sealed interface HomeEvent {
data class SelectHomeNavigationBarItem(val item: HomeNavigationBarItem) : HomeEvents data class SelectHomeNavigationBarItem(val item: HomeNavigationBarItem) : HomeEvent
data class SwitchToAccount(val sessionId: SessionId) : HomeEvents data class SwitchToAccount(val sessionId: SessionId) : HomeEvent
} }

View file

@ -34,7 +34,7 @@ import io.element.android.annotations.ContributesNode
import io.element.android.features.home.api.HomeEntryPoint import io.element.android.features.home.api.HomeEntryPoint
import io.element.android.features.home.impl.components.RoomListMenuAction import io.element.android.features.home.impl.components.RoomListMenuAction
import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.roomlist.RoomListEvents import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.features.invite.api.InviteData import io.element.android.features.invite.api.InviteData
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteView import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteView
import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint import io.element.android.features.invite.api.declineandblock.DeclineInviteAndBlockEntryPoint
@ -159,7 +159,7 @@ class HomeFlowNode(
} }
private fun onNewOwnersSelected(roomId: RoomId) { private fun onNewOwnersSelected(roomId: RoomId) {
stateFlow.value.roomListState.eventSink(RoomListEvents.LeaveRoom(roomId, needsConfirmation = false)) stateFlow.value.roomListState.eventSink(RoomListEvent.LeaveRoom(roomId, needsConfirmation = false))
} }
private fun rootNode(buildContext: BuildContext): Node { private fun rootNode(buildContext: BuildContext): Node {

View file

@ -80,15 +80,15 @@ class HomePresenter(
val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator() val showAvatarIndicator by indicatorService.showRoomListTopBarIndicator()
val directLogoutState = logoutPresenter.present() val directLogoutState = logoutPresenter.present()
fun handleEvent(event: HomeEvents) { fun handleEvent(event: HomeEvent) {
when (event) { when (event) {
is HomeEvents.SelectHomeNavigationBarItem -> coroutineState.launch { is HomeEvent.SelectHomeNavigationBarItem -> coroutineState.launch {
if (event.item == HomeNavigationBarItem.Spaces) { if (event.item == HomeNavigationBarItem.Spaces) {
announcementService.showAnnouncement(Announcement.Space) announcementService.showAnnouncement(Announcement.Space)
} }
currentHomeNavigationBarItemOrdinal = event.item.ordinal currentHomeNavigationBarItemOrdinal = event.item.ordinal
} }
is HomeEvents.SwitchToAccount -> coroutineState.launch { is HomeEvent.SwitchToAccount -> coroutineState.launch {
sessionStore.setLatestSession(event.sessionId.value) sessionStore.setLatestSession(event.sessionId.value)
} }
} }

View file

@ -29,7 +29,7 @@ data class HomeState(
val snackbarMessage: SnackbarMessage?, val snackbarMessage: SnackbarMessage?,
val canReportBug: Boolean, val canReportBug: Boolean,
val directLogoutState: DirectLogoutState, val directLogoutState: DirectLogoutState,
val eventSink: (HomeEvents) -> Unit, val eventSink: (HomeEvent) -> Unit,
) { ) {
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters

View file

@ -59,7 +59,7 @@ internal fun aHomeState(
homeSpacesState: HomeSpacesState = aHomeSpacesState(), homeSpacesState: HomeSpacesState = aHomeSpacesState(),
canReportBug: Boolean = true, canReportBug: Boolean = true,
directLogoutState: DirectLogoutState = aDirectLogoutState(), directLogoutState: DirectLogoutState = aDirectLogoutState(),
eventSink: (HomeEvents) -> Unit = {} eventSink: (HomeEvent) -> Unit = {}
) = HomeState( ) = HomeState(
currentUserAndNeighbors = currentUserAndNeighbors.toImmutableList(), currentUserAndNeighbors = currentUserAndNeighbors.toImmutableList(),
showAvatarIndicator = showAvatarIndicator, showAvatarIndicator = showAvatarIndicator,

View file

@ -47,7 +47,7 @@ import io.element.android.features.home.impl.components.RoomListMenuAction
import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.roomlist.RoomListContextMenu import io.element.android.features.home.impl.roomlist.RoomListContextMenu
import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu import io.element.android.features.home.impl.roomlist.RoomListDeclineInviteMenu
import io.element.android.features.home.impl.roomlist.RoomListEvents import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.features.home.impl.roomlist.RoomListState import io.element.android.features.home.impl.roomlist.RoomListState
import io.element.android.features.home.impl.search.RoomListSearchView import io.element.android.features.home.impl.search.RoomListSearchView
import io.element.android.features.home.impl.spaces.HomeSpacesView import io.element.android.features.home.impl.spaces.HomeSpacesView
@ -156,7 +156,7 @@ private fun HomeScaffold(
BackHandler( BackHandler(
enabled = state.currentHomeNavigationBarItem != HomeNavigationBarItem.Chats, enabled = state.currentHomeNavigationBarItem != HomeNavigationBarItem.Chats,
) { ) {
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Chats)) state.eventSink(HomeEvent.SelectHomeNavigationBarItem(HomeNavigationBarItem.Chats))
} }
val hazeState = rememberHazeState() val hazeState = rememberHazeState()
@ -172,11 +172,11 @@ private fun HomeScaffold(
currentUserAndNeighbors = state.currentUserAndNeighbors, currentUserAndNeighbors = state.currentUserAndNeighbors,
showAvatarIndicator = state.showAvatarIndicator, showAvatarIndicator = state.showAvatarIndicator,
areSearchResultsDisplayed = roomListState.searchState.isSearchActive, areSearchResultsDisplayed = roomListState.searchState.isSearchActive,
onToggleSearch = { roomListState.eventSink(RoomListEvents.ToggleSearchResults) }, onToggleSearch = { roomListState.eventSink(RoomListEvent.ToggleSearchResults) },
onMenuActionClick = onMenuActionClick, onMenuActionClick = onMenuActionClick,
onOpenSettings = onOpenSettings, onOpenSettings = onOpenSettings,
onAccountSwitch = { onAccountSwitch = {
state.eventSink(HomeEvents.SwitchToAccount(it)) state.eventSink(HomeEvent.SwitchToAccount(it))
}, },
onCreateSpace = onCreateSpaceClick, onCreateSpace = onCreateSpaceClick,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
@ -211,7 +211,7 @@ private fun HomeScaffold(
lazyListStateTarget.animateScrollToItem(0) lazyListStateTarget.animateScrollToItem(0)
} }
} else { } else {
state.eventSink(HomeEvents.SelectHomeNavigationBarItem(item)) state.eventSink(HomeEvent.SelectHomeNavigationBarItem(item))
} }
}, },
modifier = Modifier.hazeEffect( modifier = Modifier.hazeEffect(

View file

@ -47,7 +47,7 @@ import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.model.RoomSummaryDisplayType import io.element.android.features.home.impl.model.RoomSummaryDisplayType
import io.element.android.features.home.impl.roomlist.RoomListContentState import io.element.android.features.home.impl.roomlist.RoomListContentState
import io.element.android.features.home.impl.roomlist.RoomListContentStateProvider import io.element.android.features.home.impl.roomlist.RoomListContentStateProvider
import io.element.android.features.home.impl.roomlist.RoomListEvents import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.features.home.impl.roomlist.SecurityBannerState import io.element.android.features.home.impl.roomlist.SecurityBannerState
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -64,7 +64,7 @@ fun RoomListContentView(
filtersState: RoomListFiltersState, filtersState: RoomListFiltersState,
lazyListState: LazyListState, lazyListState: LazyListState,
hideInvitesAvatars: Boolean, hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit, onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit, onRoomClick: (RoomListRoomSummary) -> Unit,
@ -131,7 +131,7 @@ private fun SkeletonView(
@Composable @Composable
private fun EmptyView( private fun EmptyView(
state: RoomListContentState.Empty, state: RoomListContentState.Empty,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit, onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit,
onCreateRoomClick: () -> Unit, onCreateRoomClick: () -> Unit,
@ -155,13 +155,13 @@ private fun EmptyView(
SecurityBannerState.SetUpRecovery -> { SecurityBannerState.SetUpRecovery -> {
SetUpRecoveryKeyBanner( SetUpRecoveryKeyBanner(
onContinueClick = onSetUpRecoveryClick, onContinueClick = onSetUpRecoveryClick,
onDismissClick = { eventSink(RoomListEvents.DismissBanner) }, onDismissClick = { eventSink(RoomListEvent.DismissBanner) },
) )
} }
SecurityBannerState.RecoveryKeyConfirmation -> { SecurityBannerState.RecoveryKeyConfirmation -> {
ConfirmRecoveryKeyBanner( ConfirmRecoveryKeyBanner(
onContinueClick = onConfirmRecoveryKeyClick, onContinueClick = onConfirmRecoveryKeyClick,
onDismissClick = { eventSink(RoomListEvents.DismissBanner) }, onDismissClick = { eventSink(RoomListEvent.DismissBanner) },
) )
} }
SecurityBannerState.None -> Unit SecurityBannerState.None -> Unit
@ -175,7 +175,7 @@ private fun RoomsView(
state: RoomListContentState.Rooms, state: RoomListContentState.Rooms,
hideInvitesAvatars: Boolean, hideInvitesAvatars: Boolean,
filtersState: RoomListFiltersState, filtersState: RoomListFiltersState,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit, onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit, onRoomClick: (RoomListRoomSummary) -> Unit,
@ -207,7 +207,7 @@ private fun RoomsView(
private fun RoomsViewList( private fun RoomsViewList(
state: RoomListContentState.Rooms, state: RoomListContentState.Rooms,
hideInvitesAvatars: Boolean, hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
onSetUpRecoveryClick: () -> Unit, onSetUpRecoveryClick: () -> Unit,
onConfirmRecoveryKeyClick: () -> Unit, onConfirmRecoveryKeyClick: () -> Unit,
onRoomClick: (RoomListRoomSummary) -> Unit, onRoomClick: (RoomListRoomSummary) -> Unit,
@ -225,7 +225,7 @@ private fun RoomsViewList(
} }
val updatedEventSink by rememberUpdatedState(newValue = eventSink) val updatedEventSink by rememberUpdatedState(newValue = eventSink)
LaunchedEffect(visibleRange) { LaunchedEffect(visibleRange) {
updatedEventSink(RoomListEvents.UpdateVisibleRange(visibleRange)) updatedEventSink(RoomListEvent.UpdateVisibleRange(visibleRange))
} }
LazyColumn( LazyColumn(
state = lazyListState, state = lazyListState,
@ -237,7 +237,7 @@ private fun RoomsViewList(
item { item {
SetUpRecoveryKeyBanner( SetUpRecoveryKeyBanner(
onContinueClick = onSetUpRecoveryClick, onContinueClick = onSetUpRecoveryClick,
onDismissClick = { updatedEventSink(RoomListEvents.DismissBanner) }, onDismissClick = { updatedEventSink(RoomListEvent.DismissBanner) },
) )
} }
} }
@ -245,7 +245,7 @@ private fun RoomsViewList(
item { item {
ConfirmRecoveryKeyBanner( ConfirmRecoveryKeyBanner(
onContinueClick = onConfirmRecoveryKeyClick, onContinueClick = onConfirmRecoveryKeyClick,
onDismissClick = { updatedEventSink(RoomListEvents.DismissBanner) }, onDismissClick = { updatedEventSink(RoomListEvent.DismissBanner) },
) )
} }
} }
@ -260,7 +260,7 @@ private fun RoomsViewList(
} else if (state.showNewNotificationSoundBanner) { } else if (state.showNewNotificationSoundBanner) {
item { item {
NewNotificationSoundBanner( NewNotificationSoundBanner(
onDismissClick = { updatedEventSink(RoomListEvents.DismissNewNotificationSoundBanner) }, onDismissClick = { updatedEventSink(RoomListEvent.DismissNewNotificationSoundBanner) },
) )
} }
} }

View file

@ -44,7 +44,7 @@ import io.element.android.features.home.impl.model.LatestEvent
import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.model.RoomListRoomSummaryProvider import io.element.android.features.home.impl.model.RoomListRoomSummaryProvider
import io.element.android.features.home.impl.model.RoomSummaryDisplayType import io.element.android.features.home.impl.model.RoomSummaryDisplayType
import io.element.android.features.home.impl.roomlist.RoomListEvents import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.libraries.core.extensions.orEmpty import io.element.android.libraries.core.extensions.orEmpty
import io.element.android.libraries.core.extensions.toSafeLength import io.element.android.libraries.core.extensions.toSafeLength
import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom import io.element.android.libraries.designsystem.atomic.atoms.UnreadIndicatorAtom
@ -74,7 +74,7 @@ internal fun RoomSummaryRow(
hideInviteAvatars: Boolean, hideInviteAvatars: Boolean,
isInviteSeen: Boolean, isInviteSeen: Boolean,
onClick: (RoomListRoomSummary) -> Unit, onClick: (RoomListRoomSummary) -> Unit,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
Box(modifier = modifier) { Box(modifier = modifier) {
@ -104,10 +104,10 @@ internal fun RoomSummaryRow(
Spacer(modifier = Modifier.height(12.dp)) Spacer(modifier = Modifier.height(12.dp))
InviteButtonsRowMolecule( InviteButtonsRowMolecule(
onAcceptClick = { onAcceptClick = {
eventSink(RoomListEvents.AcceptInvite(room)) eventSink(RoomListEvent.AcceptInvite(room))
}, },
onDeclineClick = { onDeclineClick = {
eventSink(RoomListEvents.ShowDeclineInviteMenu(room)) eventSink(RoomListEvent.ShowDeclineInviteMenu(room))
} }
) )
} }
@ -117,7 +117,7 @@ internal fun RoomSummaryRow(
room = room, room = room,
onClick = onClick, onClick = onClick,
onLongClick = { onLongClick = {
eventSink(RoomListEvents.ShowContextMenu(room)) eventSink(RoomListEvent.ShowContextMenu(room))
}, },
) { ) {
NameAndTimestampRow( NameAndTimestampRow(

View file

@ -8,7 +8,7 @@
package io.element.android.features.home.impl.filters package io.element.android.features.home.impl.filters
sealed interface RoomListFiltersEvents { sealed interface RoomListFiltersEvent {
data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvents data class ToggleFilter(val filter: RoomListFilter) : RoomListFiltersEvent
data object ClearSelectedFilters : RoomListFiltersEvents data object ClearSelectedFilters : RoomListFiltersEvent
} }

View file

@ -28,12 +28,12 @@ class RoomListFiltersPresenter(
@Composable @Composable
override fun present(): RoomListFiltersState { override fun present(): RoomListFiltersState {
fun handleEvent(event: RoomListFiltersEvents) { fun handleEvent(event: RoomListFiltersEvent) {
when (event) { when (event) {
RoomListFiltersEvents.ClearSelectedFilters -> { RoomListFiltersEvent.ClearSelectedFilters -> {
filterSelectionStrategy.clear() filterSelectionStrategy.clear()
} }
is RoomListFiltersEvents.ToggleFilter -> { is RoomListFiltersEvent.ToggleFilter -> {
filterSelectionStrategy.toggle(event.filter) filterSelectionStrategy.toggle(event.filter)
} }
} }

View file

@ -14,7 +14,7 @@ import kotlinx.collections.immutable.toImmutableList
data class RoomListFiltersState( data class RoomListFiltersState(
val filterSelectionStates: ImmutableList<FilterSelectionState>, val filterSelectionStates: ImmutableList<FilterSelectionState>,
val eventSink: (RoomListFiltersEvents) -> Unit, val eventSink: (RoomListFiltersEvent) -> Unit,
) { ) {
val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected } val hasAnyFilterSelected = filterSelectionStates.any { it.isSelected }

View file

@ -24,7 +24,7 @@ class RoomListFiltersStateProvider : PreviewParameterProvider<RoomListFiltersSta
fun aRoomListFiltersState( fun aRoomListFiltersState(
filterSelectionStates: List<FilterSelectionState> = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = false) }, filterSelectionStates: List<FilterSelectionState> = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = false) },
eventSink: (RoomListFiltersEvents) -> Unit = {}, eventSink: (RoomListFiltersEvent) -> Unit = {},
) = RoomListFiltersState( ) = RoomListFiltersState(
filterSelectionStates = filterSelectionStates.toImmutableList(), filterSelectionStates = filterSelectionStates.toImmutableList(),
eventSink = eventSink, eventSink = eventSink,

View file

@ -60,11 +60,11 @@ fun RoomListFiltersView(
modifier: Modifier = Modifier modifier: Modifier = Modifier
) { ) {
fun onClearFiltersClick() { fun onClearFiltersClick() {
state.eventSink(RoomListFiltersEvents.ClearSelectedFilters) state.eventSink(RoomListFiltersEvent.ClearSelectedFilters)
} }
fun onToggleFilter(filter: RoomListFilter) { fun onToggleFilter(filter: RoomListFilter) {
state.eventSink(RoomListFiltersEvents.ToggleFilter(filter)) state.eventSink(RoomListFiltersEvent.ToggleFilter(filter))
} }
var scrollToStart by remember { mutableIntStateOf(0) } var scrollToStart by remember { mutableIntStateOf(0) }

View file

@ -37,41 +37,41 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun RoomListContextMenu( fun RoomListContextMenu(
contextMenu: RoomListState.ContextMenu.Shown, contextMenu: RoomListState.ContextMenu.Shown,
canReportRoom: Boolean, canReportRoom: Boolean,
eventSink: (RoomListEvents.ContextMenuEvents) -> Unit, eventSink: (RoomListEvent.ContextMenuEvent) -> Unit,
onRoomSettingsClick: (roomId: RoomId) -> Unit, onRoomSettingsClick: (roomId: RoomId) -> Unit,
onReportRoomClick: (roomId: RoomId) -> Unit onReportRoomClick: (roomId: RoomId) -> Unit
) { ) {
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { eventSink(RoomListEvents.HideContextMenu) }, onDismissRequest = { eventSink(RoomListEvent.HideContextMenu) },
) { ) {
RoomListModalBottomSheetContent( RoomListModalBottomSheetContent(
contextMenu = contextMenu, contextMenu = contextMenu,
canReportRoom = canReportRoom, canReportRoom = canReportRoom,
onRoomMarkReadClick = { onRoomMarkReadClick = {
eventSink(RoomListEvents.HideContextMenu) eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvents.MarkAsRead(contextMenu.roomId)) eventSink(RoomListEvent.MarkAsRead(contextMenu.roomId))
}, },
onRoomMarkUnreadClick = { onRoomMarkUnreadClick = {
eventSink(RoomListEvents.HideContextMenu) eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvents.MarkAsUnread(contextMenu.roomId)) eventSink(RoomListEvent.MarkAsUnread(contextMenu.roomId))
}, },
onRoomSettingsClick = { onRoomSettingsClick = {
eventSink(RoomListEvents.HideContextMenu) eventSink(RoomListEvent.HideContextMenu)
onRoomSettingsClick(contextMenu.roomId) onRoomSettingsClick(contextMenu.roomId)
}, },
onLeaveRoomClick = { onLeaveRoomClick = {
eventSink(RoomListEvents.HideContextMenu) eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvents.LeaveRoom(contextMenu.roomId, needsConfirmation = true)) eventSink(RoomListEvent.LeaveRoom(contextMenu.roomId, needsConfirmation = true))
}, },
onFavoriteChange = { isFavorite -> onFavoriteChange = { isFavorite ->
eventSink(RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, isFavorite)) eventSink(RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, isFavorite))
}, },
onClearCacheRoomClick = { onClearCacheRoomClick = {
eventSink(RoomListEvents.HideContextMenu) eventSink(RoomListEvent.HideContextMenu)
eventSink(RoomListEvents.ClearCacheOfRoom(contextMenu.roomId)) eventSink(RoomListEvent.ClearCacheOfRoom(contextMenu.roomId))
}, },
onReportRoomClick = { onReportRoomClick = {
eventSink(RoomListEvents.HideContextMenu) eventSink(RoomListEvent.HideContextMenu)
onReportRoomClick(contextMenu.roomId) onReportRoomClick(contextMenu.roomId)
}, },
) )
@ -131,16 +131,21 @@ private fun RoomListModalBottomSheetContent(
style = ListItemStyle.Primary, style = ListItemStyle.Primary,
) )
} }
val (textResId, icon) = if (contextMenu.isFavorite) {
CommonStrings.common_favourited to CompoundIcons.FavouriteSolid()
} else {
CommonStrings.common_favourite to CompoundIcons.Favourite()
}
ListItem( ListItem(
headlineContent = { headlineContent = {
Text( Text(
text = stringResource(id = CommonStrings.common_favourite), text = stringResource(id = textResId),
style = MaterialTheme.typography.bodyLarge, style = MaterialTheme.typography.bodyLarge,
) )
}, },
leadingContent = ListItemContent.Icon( leadingContent = ListItemContent.Icon(
iconSource = IconSource.Vector( iconSource = IconSource.Vector(
CompoundIcons.Favourite(), icon,
) )
), ),
trailingContent = ListItemContent.Switch( trailingContent = ListItemContent.Switch(

View file

@ -38,27 +38,27 @@ fun RoomListDeclineInviteMenu(
menu: RoomListState.DeclineInviteMenu.Shown, menu: RoomListState.DeclineInviteMenu.Shown,
canReportRoom: Boolean, canReportRoom: Boolean,
onDeclineAndBlockClick: (RoomListRoomSummary) -> Unit, onDeclineAndBlockClick: (RoomListRoomSummary) -> Unit,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
) { ) {
ModalBottomSheet( ModalBottomSheet(
onDismissRequest = { eventSink(RoomListEvents.HideDeclineInviteMenu) }, onDismissRequest = { eventSink(RoomListEvent.HideDeclineInviteMenu) },
) { ) {
RoomListDeclineInviteMenuContent( RoomListDeclineInviteMenuContent(
roomName = menu.roomSummary.name ?: menu.roomSummary.roomId.value, roomName = menu.roomSummary.name ?: menu.roomSummary.roomId.value,
onDeclineClick = { onDeclineClick = {
eventSink(RoomListEvents.HideDeclineInviteMenu) eventSink(RoomListEvent.HideDeclineInviteMenu)
eventSink(RoomListEvents.DeclineInvite(menu.roomSummary, false)) eventSink(RoomListEvent.DeclineInvite(menu.roomSummary, false))
}, },
onDeclineAndBlockClick = { onDeclineAndBlockClick = {
eventSink(RoomListEvents.HideDeclineInviteMenu) eventSink(RoomListEvent.HideDeclineInviteMenu)
if (canReportRoom) { if (canReportRoom) {
onDeclineAndBlockClick(menu.roomSummary) onDeclineAndBlockClick(menu.roomSummary)
} else { } else {
eventSink(RoomListEvents.DeclineInvite(menu.roomSummary, true)) eventSink(RoomListEvent.DeclineInvite(menu.roomSummary, true))
} }
}, },
onCancelClick = { onCancelClick = {
eventSink(RoomListEvents.HideDeclineInviteMenu) eventSink(RoomListEvent.HideDeclineInviteMenu)
} }
) )
} }

View file

@ -11,24 +11,24 @@ package io.element.android.features.home.impl.roomlist
import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.RoomId
sealed interface RoomListEvents { sealed interface RoomListEvent {
data class UpdateVisibleRange(val range: IntRange) : RoomListEvents data class UpdateVisibleRange(val range: IntRange) : RoomListEvent
data object DismissRequestVerificationPrompt : RoomListEvents data object DismissRequestVerificationPrompt : RoomListEvent
data object DismissBanner : RoomListEvents data object DismissBanner : RoomListEvent
data object DismissNewNotificationSoundBanner : RoomListEvents data object DismissNewNotificationSoundBanner : RoomListEvent
data object ToggleSearchResults : RoomListEvents data object ToggleSearchResults : RoomListEvent
data class ShowContextMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents data class ShowContextMenu(val roomSummary: RoomListRoomSummary) : RoomListEvent
data class AcceptInvite(val roomSummary: RoomListRoomSummary) : RoomListEvents data class AcceptInvite(val roomSummary: RoomListRoomSummary) : RoomListEvent
data class DeclineInvite(val roomSummary: RoomListRoomSummary, val blockUser: Boolean) : RoomListEvents data class DeclineInvite(val roomSummary: RoomListRoomSummary, val blockUser: Boolean) : RoomListEvent
data class ShowDeclineInviteMenu(val roomSummary: RoomListRoomSummary) : RoomListEvents data class ShowDeclineInviteMenu(val roomSummary: RoomListRoomSummary) : RoomListEvent
data object HideDeclineInviteMenu : RoomListEvents data object HideDeclineInviteMenu : RoomListEvent
sealed interface ContextMenuEvents : RoomListEvents sealed interface ContextMenuEvent : RoomListEvent
data object HideContextMenu : ContextMenuEvents data object HideContextMenu : ContextMenuEvent
data class LeaveRoom(val roomId: RoomId, val needsConfirmation: Boolean) : ContextMenuEvents data class LeaveRoom(val roomId: RoomId, val needsConfirmation: Boolean) : ContextMenuEvent
data class MarkAsRead(val roomId: RoomId) : ContextMenuEvents data class MarkAsRead(val roomId: RoomId) : ContextMenuEvent
data class MarkAsUnread(val roomId: RoomId) : ContextMenuEvents data class MarkAsUnread(val roomId: RoomId) : ContextMenuEvent
data class SetRoomIsFavorite(val roomId: RoomId, val isFavorite: Boolean) : ContextMenuEvents data class SetRoomIsFavorite(val roomId: RoomId, val isFavorite: Boolean) : ContextMenuEvent
data class ClearCacheOfRoom(val roomId: RoomId) : ContextMenuEvents data class ClearCacheOfRoom(val roomId: RoomId) : ContextMenuEvent
} }

View file

@ -29,7 +29,7 @@ import io.element.android.features.announcement.api.Announcement
import io.element.android.features.announcement.api.AnnouncementService import io.element.android.features.announcement.api.AnnouncementService
import io.element.android.features.home.impl.datasource.RoomListDataSource import io.element.android.features.home.impl.datasource.RoomListDataSource
import io.element.android.features.home.impl.filters.RoomListFiltersState import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.search.RoomListSearchEvents import io.element.android.features.home.impl.search.RoomListSearchEvent
import io.element.android.features.home.impl.search.RoomListSearchState import io.element.android.features.home.impl.search.RoomListSearchState
import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.SeenInvitesStore
import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.AcceptInvite import io.element.android.features.invite.api.acceptdecline.AcceptDeclineInviteEvents.AcceptInvite
@ -116,42 +116,42 @@ class RoomListPresenter(
val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) } val contextMenu = remember { mutableStateOf<RoomListState.ContextMenu>(RoomListState.ContextMenu.Hidden) }
val declineInviteMenu = remember { mutableStateOf<RoomListState.DeclineInviteMenu>(RoomListState.DeclineInviteMenu.Hidden) } val declineInviteMenu = remember { mutableStateOf<RoomListState.DeclineInviteMenu>(RoomListState.DeclineInviteMenu.Hidden) }
fun handleEvent(event: RoomListEvents) { fun handleEvent(event: RoomListEvent) {
when (event) { when (event) {
is RoomListEvents.UpdateVisibleRange -> coroutineScope.launch { is RoomListEvent.UpdateVisibleRange -> coroutineScope.launch {
updateVisibleRange(event.range) updateVisibleRange(event.range)
} }
RoomListEvents.DismissRequestVerificationPrompt -> securityBannerDismissed = true RoomListEvent.DismissRequestVerificationPrompt -> securityBannerDismissed = true
RoomListEvents.DismissBanner -> securityBannerDismissed = true RoomListEvent.DismissBanner -> securityBannerDismissed = true
RoomListEvents.DismissNewNotificationSoundBanner -> coroutineScope.launch { RoomListEvent.DismissNewNotificationSoundBanner -> coroutineScope.launch {
announcementService.onAnnouncementDismissed(Announcement.NewNotificationSound) announcementService.onAnnouncementDismissed(Announcement.NewNotificationSound)
} }
RoomListEvents.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvents.ToggleSearchVisibility) RoomListEvent.ToggleSearchResults -> searchState.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
is RoomListEvents.ShowContextMenu -> { is RoomListEvent.ShowContextMenu -> {
coroutineScope.showContextMenu(event, contextMenu) coroutineScope.showContextMenu(event, contextMenu)
} }
is RoomListEvents.HideContextMenu -> { is RoomListEvent.HideContextMenu -> {
contextMenu.value = RoomListState.ContextMenu.Hidden contextMenu.value = RoomListState.ContextMenu.Hidden
} }
is RoomListEvents.LeaveRoom -> { is RoomListEvent.LeaveRoom -> {
leaveRoomState.eventSink(LeaveRoomEvent.LeaveRoom(event.roomId, needsConfirmation = event.needsConfirmation)) leaveRoomState.eventSink(LeaveRoomEvent.LeaveRoom(event.roomId, needsConfirmation = event.needsConfirmation))
} }
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.setRoomIsFavorite(event.roomId, event.isFavorite) is RoomListEvent.SetRoomIsFavorite -> coroutineScope.setRoomIsFavorite(event.roomId, event.isFavorite)
is RoomListEvents.MarkAsRead -> coroutineScope.markAsRead(event.roomId) is RoomListEvent.MarkAsRead -> coroutineScope.markAsRead(event.roomId)
is RoomListEvents.MarkAsUnread -> coroutineScope.markAsUnread(event.roomId) is RoomListEvent.MarkAsUnread -> coroutineScope.markAsUnread(event.roomId)
is RoomListEvents.AcceptInvite -> { is RoomListEvent.AcceptInvite -> {
acceptDeclineInviteState.eventSink( acceptDeclineInviteState.eventSink(
AcceptInvite(event.roomSummary.toInviteData()) AcceptInvite(event.roomSummary.toInviteData())
) )
} }
is RoomListEvents.DeclineInvite -> { is RoomListEvent.DeclineInvite -> {
acceptDeclineInviteState.eventSink( acceptDeclineInviteState.eventSink(
DeclineInvite(event.roomSummary.toInviteData(), blockUser = event.blockUser, shouldConfirm = false) DeclineInvite(event.roomSummary.toInviteData(), blockUser = event.blockUser, shouldConfirm = false)
) )
} }
is RoomListEvents.ShowDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Shown(event.roomSummary) is RoomListEvent.ShowDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Shown(event.roomSummary)
RoomListEvents.HideDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Hidden RoomListEvent.HideDeclineInviteMenu -> declineInviteMenu.value = RoomListState.DeclineInviteMenu.Hidden
is RoomListEvents.ClearCacheOfRoom -> coroutineScope.clearCacheOfRoom(event.roomId) is RoomListEvent.ClearCacheOfRoom -> coroutineScope.clearCacheOfRoom(event.roomId)
} }
} }
@ -253,7 +253,7 @@ class RoomListPresenter(
} }
@OptIn(ExperimentalCoroutinesApi::class) @OptIn(ExperimentalCoroutinesApi::class)
private fun CoroutineScope.showContextMenu(event: RoomListEvents.ShowContextMenu, contextMenuState: MutableState<RoomListState.ContextMenu>) = launch { private fun CoroutineScope.showContextMenu(event: RoomListEvent.ShowContextMenu, contextMenuState: MutableState<RoomListState.ContextMenu>) = launch {
val initialState = RoomListState.ContextMenu.Shown( val initialState = RoomListState.ContextMenu.Shown(
roomId = event.roomSummary.roomId, roomId = event.roomSummary.roomId,
roomName = event.roomSummary.name, roomName = event.roomSummary.name,

View file

@ -30,7 +30,7 @@ data class RoomListState(
val acceptDeclineInviteState: AcceptDeclineInviteState, val acceptDeclineInviteState: AcceptDeclineInviteState,
val hideInvitesAvatars: Boolean, val hideInvitesAvatars: Boolean,
val canReportRoom: Boolean, val canReportRoom: Boolean,
val eventSink: (RoomListEvents) -> Unit, val eventSink: (RoomListEvent) -> Unit,
) { ) {
val displayFilters = contentState is RoomListContentState.Rooms val displayFilters = contentState is RoomListContentState.Rooms

View file

@ -15,7 +15,7 @@ open class RoomListStateContextMenuShownProvider : PreviewParameterProvider<Room
override val values: Sequence<RoomListState.ContextMenu.Shown> override val values: Sequence<RoomListState.ContextMenu.Shown>
get() = sequenceOf( get() = sequenceOf(
aContextMenuShown(hasNewContent = true), aContextMenuShown(hasNewContent = true),
aContextMenuShown(isDm = true), aContextMenuShown(isDm = true, isFavorite = true),
aContextMenuShown(roomName = null) aContextMenuShown(roomName = null)
) )
} }

View file

@ -56,7 +56,7 @@ internal fun aRoomListState(
acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(), acceptDeclineInviteState: AcceptDeclineInviteState = anAcceptDeclineInviteState(),
hideInvitesAvatars: Boolean = false, hideInvitesAvatars: Boolean = false,
canReportRoom: Boolean = true, canReportRoom: Boolean = true,
eventSink: (RoomListEvents) -> Unit = {} eventSink: (RoomListEvent) -> Unit = {}
) = RoomListState( ) = RoomListState(
contextMenu = contextMenu, contextMenu = contextMenu,
declineInviteMenu = declineInviteMenu, declineInviteMenu = declineInviteMenu,

View file

@ -8,7 +8,7 @@
package io.element.android.features.home.impl.search package io.element.android.features.home.impl.search
sealed interface RoomListSearchEvents { sealed interface RoomListSearchEvent {
data object ToggleSearchVisibility : RoomListSearchEvents data object ToggleSearchVisibility : RoomListSearchEvent
data object ClearQuery : RoomListSearchEvents data object ClearQuery : RoomListSearchEvent
} }

View file

@ -45,12 +45,12 @@ class RoomListSearchPresenter(
dataSource.setSearchQuery(searchQuery.text.toString()) dataSource.setSearchQuery(searchQuery.text.toString())
} }
fun handleEvent(event: RoomListSearchEvents) { fun handleEvent(event: RoomListSearchEvent) {
when (event) { when (event) {
RoomListSearchEvents.ClearQuery -> { RoomListSearchEvent.ClearQuery -> {
searchQuery.clearText() searchQuery.clearText()
} }
RoomListSearchEvents.ToggleSearchVisibility -> { RoomListSearchEvent.ToggleSearchVisibility -> {
isSearchActive = !isSearchActive isSearchActive = !isSearchActive
searchQuery.clearText() searchQuery.clearText()
} }

View file

@ -16,5 +16,5 @@ data class RoomListSearchState(
val isSearchActive: Boolean, val isSearchActive: Boolean,
val query: TextFieldState, val query: TextFieldState,
val results: ImmutableList<RoomListRoomSummary>, val results: ImmutableList<RoomListRoomSummary>,
val eventSink: (RoomListSearchEvents) -> Unit val eventSink: (RoomListSearchEvent) -> Unit
) )

View file

@ -31,7 +31,7 @@ fun aRoomListSearchState(
isSearchActive: Boolean = false, isSearchActive: Boolean = false,
query: String = "", query: String = "",
results: ImmutableList<RoomListRoomSummary> = persistentListOf(), results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
eventSink: (RoomListSearchEvents) -> Unit = { }, eventSink: (RoomListSearchEvent) -> Unit = { },
) = RoomListSearchState( ) = RoomListSearchState(
isSearchActive = isSearchActive, isSearchActive = isSearchActive,
query = TextFieldState(initialText = query), query = TextFieldState(initialText = query),

View file

@ -38,7 +38,7 @@ import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.home.impl.components.RoomSummaryRow import io.element.android.features.home.impl.components.RoomSummaryRow
import io.element.android.features.home.impl.contentType import io.element.android.features.home.impl.contentType
import io.element.android.features.home.impl.model.RoomListRoomSummary import io.element.android.features.home.impl.model.RoomListRoomSummary
import io.element.android.features.home.impl.roomlist.RoomListEvents import io.element.android.features.home.impl.roomlist.RoomListEvent
import io.element.android.libraries.designsystem.components.button.BackButton import io.element.android.libraries.designsystem.components.button.BackButton
import io.element.android.libraries.designsystem.preview.ElementPreview import io.element.android.libraries.designsystem.preview.ElementPreview
import io.element.android.libraries.designsystem.preview.PreviewsDayNight import io.element.android.libraries.designsystem.preview.PreviewsDayNight
@ -54,12 +54,12 @@ import io.element.android.libraries.ui.strings.CommonStrings
internal fun RoomListSearchView( internal fun RoomListSearchView(
state: RoomListSearchState, state: RoomListSearchState,
hideInvitesAvatars: Boolean, hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
onRoomClick: (RoomId) -> Unit, onRoomClick: (RoomId) -> Unit,
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
BackHandler(enabled = state.isSearchActive) { BackHandler(enabled = state.isSearchActive) {
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility) state.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
} }
AnimatedVisibility( AnimatedVisibility(
@ -83,13 +83,13 @@ internal fun RoomListSearchView(
private fun RoomListSearchContent( private fun RoomListSearchContent(
state: RoomListSearchState, state: RoomListSearchState,
hideInvitesAvatars: Boolean, hideInvitesAvatars: Boolean,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
onRoomClick: (RoomId) -> Unit, onRoomClick: (RoomId) -> Unit,
) { ) {
val borderColor = MaterialTheme.colorScheme.tertiary val borderColor = MaterialTheme.colorScheme.tertiary
val strokeWidth = 1.dp val strokeWidth = 1.dp
fun onBackButtonClick() { fun onBackButtonClick() {
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility) state.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
} }
fun onRoomClick(room: RoomListRoomSummary) { fun onRoomClick(room: RoomListRoomSummary) {
@ -127,7 +127,7 @@ private fun RoomListSearchContent(
), ),
trailingIcon = if (state.query.text.isNotEmpty()) { trailingIcon = if (state.query.text.isNotEmpty()) {
@Composable { @Composable {
IconButton(onClick = { state.eventSink(RoomListSearchEvents.ClearQuery) }) { IconButton(onClick = { state.eventSink(RoomListSearchEvent.ClearQuery) }) {
Icon( Icon(
imageVector = CompoundIcons.Close(), imageVector = CompoundIcons.Close(),
contentDescription = stringResource(CommonStrings.action_cancel) contentDescription = stringResource(CommonStrings.action_cancel)

View file

@ -8,9 +8,6 @@
package io.element.android.features.home.impl package io.element.android.features.home.impl
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.announcement.api.Announcement import io.element.android.features.announcement.api.Announcement
import io.element.android.features.announcement.api.AnnouncementService import io.element.android.features.announcement.api.AnnouncementService
@ -70,9 +67,7 @@ class HomePresenterTest {
), ),
), ),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo( assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(
MatrixUser(A_USER_ID, null, null) MatrixUser(A_USER_ID, null, null)
@ -96,9 +91,7 @@ class HomePresenterTest {
updateUserProfileResult = { _, _, _ -> }, updateUserProfileResult = { _, _, _ -> },
), ),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.canReportBug).isFalse() assertThat(initialState.canReportBug).isFalse()
val finalState = awaitItem() val finalState = awaitItem()
@ -115,9 +108,7 @@ class HomePresenterTest {
updateUserProfileResult = { _, _, _ -> }, updateUserProfileResult = { _, _, _ -> },
), ),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.showAvatarIndicator).isFalse() assertThat(initialState.showAvatarIndicator).isFalse()
indicatorService.setShowRoomListTopBarIndicator(true) indicatorService.setShowRoomListTopBarIndicator(true)
@ -139,9 +130,7 @@ class HomePresenterTest {
updateUserProfileResult = { _, _, _ -> }, updateUserProfileResult = { _, _, _ -> },
), ),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(MatrixUser(matrixClient.sessionId)) assertThat(initialState.currentUserAndNeighbors.first()).isEqualTo(MatrixUser(matrixClient.sessionId))
// No new state is coming // No new state is coming
@ -159,12 +148,10 @@ class HomePresenterTest {
showAnnouncementResult = showAnnouncementResult, showAnnouncementResult = showAnnouncementResult,
) )
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats) assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats)
initialState.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces)) initialState.eventSink(HomeEvent.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces) assertThat(finalState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
showAnnouncementResult.assertions().isCalledOnce() showAnnouncementResult.assertions().isCalledOnce()
@ -189,7 +176,7 @@ class HomePresenterTest {
assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats) assertThat(initialState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Chats)
assertThat(initialState.showNavigationBar).isTrue() assertThat(initialState.showNavigationBar).isTrue()
// User navigate to Spaces // User navigate to Spaces
initialState.eventSink(HomeEvents.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces)) initialState.eventSink(HomeEvent.SelectHomeNavigationBarItem(HomeNavigationBarItem.Spaces))
val spaceState = awaitItem() val spaceState = awaitItem()
assertThat(spaceState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces) assertThat(spaceState.currentHomeNavigationBarItem).isEqualTo(HomeNavigationBarItem.Spaces)
// The last space is left // The last space is left

View file

@ -8,15 +8,13 @@
package io.element.android.features.home.impl.filters package io.element.android.features.home.impl.filters
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.home.impl.filters.selection.DefaultFilterSelectionStrategy import io.element.android.features.home.impl.filters.selection.DefaultFilterSelectionStrategy
import io.element.android.features.home.impl.filters.selection.FilterSelectionState import io.element.android.features.home.impl.filters.selection.FilterSelectionState
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.awaitLastSequentialItem
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListFilter as MatrixRoomListFilter
@ -25,9 +23,7 @@ class RoomListFiltersPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createRoomListFiltersPresenter() val presenter = createRoomListFiltersPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
awaitItem().let { state -> awaitItem().let { state ->
assertThat(state.hasAnyFilterSelected).isFalse() assertThat(state.hasAnyFilterSelected).isFalse()
assertThat(state.filterSelectionStates).containsExactly( assertThat(state.filterSelectionStates).containsExactly(
@ -46,10 +42,8 @@ class RoomListFiltersPresenterTest {
fun `present - toggle rooms filter`() = runTest { fun `present - toggle rooms filter`() = runTest {
val roomListService = FakeRoomListService() val roomListService = FakeRoomListService()
val presenter = createRoomListFiltersPresenter(roomListService) val presenter = createRoomListFiltersPresenter(roomListService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present() awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
}.test {
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
awaitLastSequentialItem().let { state -> awaitLastSequentialItem().let { state ->
assertThat(state.hasAnyFilterSelected).isTrue() assertThat(state.hasAnyFilterSelected).isTrue()
@ -66,7 +60,7 @@ class RoomListFiltersPresenterTest {
assertThat(roomListCurrentFilter.filters).containsExactly( assertThat(roomListCurrentFilter.filters).containsExactly(
MatrixRoomListFilter.Category.Group, MatrixRoomListFilter.Category.Group,
) )
state.eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms)) state.eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
} }
awaitLastSequentialItem().let { state -> awaitLastSequentialItem().let { state ->
assertThat(state.hasAnyFilterSelected).isFalse() assertThat(state.hasAnyFilterSelected).isFalse()
@ -88,13 +82,11 @@ class RoomListFiltersPresenterTest {
fun `present - clear filters event`() = runTest { fun `present - clear filters event`() = runTest {
val roomListService = FakeRoomListService() val roomListService = FakeRoomListService()
val presenter = createRoomListFiltersPresenter(roomListService) val presenter = createRoomListFiltersPresenter(roomListService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present() awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
}.test {
awaitItem().eventSink.invoke(RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms))
awaitLastSequentialItem().let { state -> awaitLastSequentialItem().let { state ->
assertThat(state.hasAnyFilterSelected).isTrue() assertThat(state.hasAnyFilterSelected).isTrue()
state.eventSink.invoke(RoomListFiltersEvents.ClearSelectedFilters) state.eventSink.invoke(RoomListFiltersEvent.ClearSelectedFilters)
} }
awaitLastSequentialItem().let { state -> awaitLastSequentialItem().let { state ->
assertThat(state.hasAnyFilterSelected).isFalse() assertThat(state.hasAnyFilterSelected).isFalse()

View file

@ -27,7 +27,7 @@ class RoomListFiltersViewTest {
@Test @Test
fun `clicking on filters generates expected Event`() { fun `clicking on filters generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListFiltersEvents>() val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
rule.setContent { rule.setContent {
RoomListFiltersView( RoomListFiltersView(
state = aRoomListFiltersState(eventSink = eventsRecorder), state = aRoomListFiltersState(eventSink = eventsRecorder),
@ -36,14 +36,14 @@ class RoomListFiltersViewTest {
rule.clickOn(R.string.screen_roomlist_filter_rooms) rule.clickOn(R.string.screen_roomlist_filter_rooms)
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListFiltersEvents.ToggleFilter(RoomListFilter.Rooms), RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms),
) )
) )
} }
@Test @Test
fun `clicking on clear filters generates expected Event`() { fun `clicking on clear filters generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListFiltersEvents>() val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
rule.setContent { rule.setContent {
RoomListFiltersView( RoomListFiltersView(
state = aRoomListFiltersState( state = aRoomListFiltersState(
@ -55,7 +55,7 @@ class RoomListFiltersViewTest {
rule.pressTag(TestTags.homeScreenClearFilters.value) rule.pressTag(TestTags.homeScreenClearFilters.value)
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListFiltersEvents.ClearSelectedFilters, RoomListFiltersEvent.ClearSelectedFilters,
) )
) )
} }

View file

@ -30,7 +30,7 @@ class RoomListContextMenuTest {
@Test @Test
fun `clicking on Mark as read generates expected Events`() { fun `clicking on Mark as read generates expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown(hasNewContent = true) val contextMenu = aContextMenuShown(hasNewContent = true)
rule.setRoomListContextMenu( rule.setRoomListContextMenu(
contextMenu = contextMenu, contextMenu = contextMenu,
@ -39,15 +39,15 @@ class RoomListContextMenuTest {
rule.clickOn(R.string.screen_roomlist_mark_as_read) rule.clickOn(R.string.screen_roomlist_mark_as_read)
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListEvents.HideContextMenu, RoomListEvent.HideContextMenu,
RoomListEvents.MarkAsRead(contextMenu.roomId), RoomListEvent.MarkAsRead(contextMenu.roomId),
) )
) )
} }
@Test @Test
fun `clicking on Mark as unread generates expected Events`() { fun `clicking on Mark as unread generates expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown(hasNewContent = false) val contextMenu = aContextMenuShown(hasNewContent = false)
rule.setRoomListContextMenu( rule.setRoomListContextMenu(
contextMenu = contextMenu, contextMenu = contextMenu,
@ -56,15 +56,15 @@ class RoomListContextMenuTest {
rule.clickOn(R.string.screen_roomlist_mark_as_unread) rule.clickOn(R.string.screen_roomlist_mark_as_unread)
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListEvents.HideContextMenu, RoomListEvent.HideContextMenu,
RoomListEvents.MarkAsUnread(contextMenu.roomId), RoomListEvent.MarkAsUnread(contextMenu.roomId),
) )
) )
} }
@Test @Test
fun `clicking on Leave room generates expected Events`() { fun `clicking on Leave room generates expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown(isDm = false) val contextMenu = aContextMenuShown(isDm = false)
rule.setRoomListContextMenu( rule.setRoomListContextMenu(
contextMenu = contextMenu, contextMenu = contextMenu,
@ -73,15 +73,15 @@ class RoomListContextMenuTest {
rule.clickOn(CommonStrings.action_leave_room) rule.clickOn(CommonStrings.action_leave_room)
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListEvents.HideContextMenu, RoomListEvent.HideContextMenu,
RoomListEvents.LeaveRoom(contextMenu.roomId, needsConfirmation = true), RoomListEvent.LeaveRoom(contextMenu.roomId, needsConfirmation = true),
) )
) )
} }
@Test @Test
fun `clicking on Report room invokes the expected callback and generates expected Event`() { fun `clicking on Report room invokes the expected callback and generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown() val contextMenu = aContextMenuShown()
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit) val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
rule.setRoomListContextMenu( rule.setRoomListContextMenu(
@ -92,13 +92,13 @@ class RoomListContextMenuTest {
onReportRoomClick = callback, onReportRoomClick = callback,
) )
rule.clickOn(CommonStrings.action_report_room) rule.clickOn(CommonStrings.action_report_room)
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu) eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
callback.assertSuccess() callback.assertSuccess()
} }
@Test @Test
fun `clicking on Settings invokes the expected callback and generates expected Event`() { fun `clicking on Settings invokes the expected callback and generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown() val contextMenu = aContextMenuShown()
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit) val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
rule.setRoomListContextMenu( rule.setRoomListContextMenu(
@ -107,13 +107,13 @@ class RoomListContextMenuTest {
onRoomSettingsClick = callback, onRoomSettingsClick = callback,
) )
rule.clickOn(CommonStrings.common_settings) rule.clickOn(CommonStrings.common_settings)
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu) eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
callback.assertSuccess() callback.assertSuccess()
} }
@Test @Test
fun `clicking on Favourites generates expected Event`() { fun `clicking on Favourites generates expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val contextMenu = aContextMenuShown(isDm = false, isFavorite = false) val contextMenu = aContextMenuShown(isDm = false, isFavorite = false)
val callback = EnsureNeverCalledWithParam<RoomId>() val callback = EnsureNeverCalledWithParam<RoomId>()
rule.setRoomListContextMenu( rule.setRoomListContextMenu(
@ -124,7 +124,7 @@ class RoomListContextMenuTest {
rule.clickOn(CommonStrings.common_favourite) rule.clickOn(CommonStrings.common_favourite)
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListEvents.SetRoomIsFavorite(contextMenu.roomId, true), RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, true),
) )
) )
} }
@ -132,7 +132,7 @@ class RoomListContextMenuTest {
private fun AndroidComposeTestRule<*, *>.setRoomListContextMenu( private fun AndroidComposeTestRule<*, *>.setRoomListContextMenu(
contextMenu: RoomListState.ContextMenu.Shown, contextMenu: RoomListState.ContextMenu.Shown,
canReportRoom: Boolean = false, canReportRoom: Boolean = false,
eventSink: (RoomListEvents) -> Unit, eventSink: (RoomListEvent) -> Unit,
onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onRoomSettingsClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
onReportRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onReportRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
) { ) {

View file

@ -28,7 +28,7 @@ class RoomListDeclineInviteMenuTest {
@Test @Test
fun `clicking on decline emits the expected Events`() { fun `clicking on decline emits the expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary()) val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
rule.setSafeContent { rule.setSafeContent {
RoomListDeclineInviteMenu( RoomListDeclineInviteMenu(
@ -41,15 +41,15 @@ class RoomListDeclineInviteMenuTest {
rule.clickOn(CommonStrings.action_decline) rule.clickOn(CommonStrings.action_decline)
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListEvents.HideDeclineInviteMenu, RoomListEvent.HideDeclineInviteMenu,
RoomListEvents.DeclineInvite(menu.roomSummary, blockUser = false), RoomListEvent.DeclineInvite(menu.roomSummary, blockUser = false),
) )
) )
} }
@Test @Test
fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() { fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary()) val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
rule.setSafeContent { rule.setSafeContent {
RoomListDeclineInviteMenu( RoomListDeclineInviteMenu(
@ -60,13 +60,13 @@ class RoomListDeclineInviteMenuTest {
) )
} }
rule.clickOn(CommonStrings.action_decline_and_block) rule.clickOn(CommonStrings.action_decline_and_block)
val expectedEvents = listOf(RoomListEvents.HideDeclineInviteMenu) val expectedEvents = listOf(RoomListEvent.HideDeclineInviteMenu)
eventsRecorder.assertList(expectedEvents) eventsRecorder.assertList(expectedEvents)
} }
@Test @Test
fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() { fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary()) val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
rule.setSafeContent { rule.setSafeContent {
RoomListDeclineInviteMenu( RoomListDeclineInviteMenu(
@ -78,15 +78,15 @@ class RoomListDeclineInviteMenuTest {
} }
rule.clickOn(CommonStrings.action_decline_and_block) rule.clickOn(CommonStrings.action_decline_and_block)
val expectedEvents = listOf( val expectedEvents = listOf(
RoomListEvents.HideDeclineInviteMenu, RoomListEvent.HideDeclineInviteMenu,
RoomListEvents.DeclineInvite(menu.roomSummary, blockUser = true), RoomListEvent.DeclineInvite(menu.roomSummary, blockUser = true),
) )
eventsRecorder.assertList(expectedEvents) eventsRecorder.assertList(expectedEvents)
} }
@Test @Test
fun `clicking on cancel emits the expected Event`() { fun `clicking on cancel emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary()) val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
rule.setSafeContent { rule.setSafeContent {
RoomListDeclineInviteMenu( RoomListDeclineInviteMenu(
@ -97,6 +97,6 @@ class RoomListDeclineInviteMenuTest {
) )
} }
rule.clickOn(CommonStrings.action_cancel) rule.clickOn(CommonStrings.action_cancel)
eventsRecorder.assertList(listOf(RoomListEvents.HideDeclineInviteMenu)) eventsRecorder.assertList(listOf(RoomListEvent.HideDeclineInviteMenu))
} }
} }

View file

@ -8,9 +8,6 @@
package io.element.android.features.home.impl.roomlist package io.element.android.features.home.impl.roomlist
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 im.vector.app.features.analytics.plan.Interaction import im.vector.app.features.analytics.plan.Interaction
import io.element.android.features.announcement.api.Announcement import io.element.android.features.announcement.api.Announcement
@ -21,7 +18,7 @@ import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFact
import io.element.android.features.home.impl.filters.RoomListFiltersState import io.element.android.features.home.impl.filters.RoomListFiltersState
import io.element.android.features.home.impl.filters.aRoomListFiltersState import io.element.android.features.home.impl.filters.aRoomListFiltersState
import io.element.android.features.home.impl.model.createRoomListRoomSummary import io.element.android.features.home.impl.model.createRoomListRoomSummary
import io.element.android.features.home.impl.search.RoomListSearchEvents import io.element.android.features.home.impl.search.RoomListSearchEvent
import io.element.android.features.home.impl.search.RoomListSearchState import io.element.android.features.home.impl.search.RoomListSearchState
import io.element.android.features.home.impl.search.aRoomListSearchState import io.element.android.features.home.impl.search.aRoomListSearchState
import io.element.android.features.invite.api.SeenInvitesStore import io.element.android.features.invite.api.SeenInvitesStore
@ -141,15 +138,13 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter( val presenter = createRoomListPresenter(
client = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService, syncService = syncService), client = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService, syncService = syncService),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val eventWithContentAsRooms = consumeItemsUntilPredicate { val eventWithContentAsRooms = consumeItemsUntilPredicate {
it.contentState is RoomListContentState.Rooms it.contentState is RoomListContentState.Rooms
}.last() }.last()
val eventSink = eventWithContentAsRooms.eventSink val eventSink = eventWithContentAsRooms.eventSink
assertThat(eventWithContentAsRooms.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation) assertThat(eventWithContentAsRooms.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
eventSink(RoomListEvents.DismissRequestVerificationPrompt) eventSink(RoomListEvent.DismissRequestVerificationPrompt)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None) assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
} }
} }
@ -173,9 +168,7 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter( val presenter = createRoomListPresenter(
client = matrixClient, client = matrixClient,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = consumeItemsUntilPredicate { val initialState = consumeItemsUntilPredicate {
it.contentState is RoomListContentState.Rooms it.contentState is RoomListContentState.Rooms
}.last() }.last()
@ -194,7 +187,7 @@ class RoomListPresenterTest {
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None) assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
encryptionService.emitRecoveryState(RecoveryState.DISABLED) encryptionService.emitRecoveryState(RecoveryState.DISABLED)
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SetUpRecovery) assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.SetUpRecovery)
nextState.eventSink(RoomListEvents.DismissBanner) nextState.eventSink(RoomListEvent.DismissBanner)
val finalState = awaitItem() val finalState = awaitItem()
assertThat(finalState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None) assertThat(finalState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
} }
@ -207,12 +200,10 @@ class RoomListPresenterTest {
givenGetRoomResult(A_ROOM_ID, room) givenGetRoomResult(A_ROOM_ID, room)
} }
val presenter = createRoomListPresenter(client = client) val presenter = createRoomListPresenter(client = client)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val summary = createRoomListRoomSummary() val summary = createRoomListRoomSummary()
initialState.eventSink(RoomListEvents.ShowContextMenu(summary)) initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.contextMenu) assertThat(state.contextMenu)
@ -257,7 +248,7 @@ class RoomListPresenterTest {
presenter.test { presenter.test {
val initialState = awaitItem() val initialState = awaitItem()
val summary = createRoomListRoomSummary() val summary = createRoomListRoomSummary()
initialState.eventSink(RoomListEvents.ShowContextMenu(summary)) initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
awaitItem().also { state -> awaitItem().also { state ->
assertThat(state.contextMenu) assertThat(state.contextMenu)
.isEqualTo( .isEqualTo(
@ -282,12 +273,10 @@ class RoomListPresenterTest {
givenGetRoomResult(A_ROOM_ID, room) givenGetRoomResult(A_ROOM_ID, room)
} }
val presenter = createRoomListPresenter(client = client) val presenter = createRoomListPresenter(client = client)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
val summary = createRoomListRoomSummary() val summary = createRoomListRoomSummary()
initialState.eventSink(RoomListEvents.ShowContextMenu(summary)) initialState.eventSink(RoomListEvent.ShowContextMenu(summary))
val shownState = awaitItem() val shownState = awaitItem()
assertThat(shownState.contextMenu) assertThat(shownState.contextMenu)
@ -302,7 +291,7 @@ class RoomListPresenterTest {
) )
) )
shownState.eventSink(RoomListEvents.HideContextMenu) shownState.eventSink(RoomListEvent.HideContextMenu)
val hiddenState = awaitItem() val hiddenState = awaitItem()
assertThat(hiddenState.contextMenu).isEqualTo(RoomListState.ContextMenu.Hidden) assertThat(hiddenState.contextMenu).isEqualTo(RoomListState.ContextMenu.Hidden)
@ -315,11 +304,9 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter( val presenter = createRoomListPresenter(
leaveRoomState = aLeaveRoomState(eventSink = leaveRoomEventsRecorder), leaveRoomState = aLeaveRoomState(eventSink = leaveRoomEventsRecorder),
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(RoomListEvents.LeaveRoom(A_ROOM_ID, needsConfirmation = true)) initialState.eventSink(RoomListEvent.LeaveRoom(A_ROOM_ID, needsConfirmation = true))
leaveRoomEventsRecorder.assertSingle(LeaveRoomEvent.LeaveRoom(A_ROOM_ID, needsConfirmation = true)) leaveRoomEventsRecorder.assertSingle(LeaveRoomEvent.LeaveRoom(A_ROOM_ID, needsConfirmation = true))
cancelAndIgnoreRemainingEvents() cancelAndIgnoreRemainingEvents()
} }
@ -327,7 +314,7 @@ class RoomListPresenterTest {
@Test @Test
fun `present - toggle search menu`() = runTest { fun `present - toggle search menu`() = runTest {
val eventRecorder = EventsRecorder<RoomListSearchEvents>() val eventRecorder = EventsRecorder<RoomListSearchEvent>()
val searchPresenter: Presenter<RoomListSearchState> = Presenter { val searchPresenter: Presenter<RoomListSearchState> = Presenter {
aRoomListSearchState( aRoomListSearchState(
eventSink = eventRecorder eventSink = eventRecorder
@ -336,20 +323,18 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter( val presenter = createRoomListPresenter(
searchPresenter = searchPresenter, searchPresenter = searchPresenter,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
eventRecorder.assertEmpty() eventRecorder.assertEmpty()
initialState.eventSink(RoomListEvents.ToggleSearchResults) initialState.eventSink(RoomListEvent.ToggleSearchResults)
eventRecorder.assertSingle( eventRecorder.assertSingle(
RoomListSearchEvents.ToggleSearchVisibility RoomListSearchEvent.ToggleSearchVisibility
) )
initialState.eventSink(RoomListEvents.ToggleSearchResults) initialState.eventSink(RoomListEvent.ToggleSearchResults)
eventRecorder.assertList( eventRecorder.assertList(
listOf( listOf(
RoomListSearchEvents.ToggleSearchVisibility, RoomListSearchEvent.ToggleSearchVisibility,
RoomListSearchEvents.ToggleSearchVisibility RoomListSearchEvent.ToggleSearchVisibility
) )
) )
} }
@ -367,9 +352,7 @@ class RoomListPresenterTest {
notificationSettingsService = notificationSettingsService notificationSettingsService = notificationSettingsService
) )
val presenter = createRoomListPresenter(client = matrixClient) val presenter = createRoomListPresenter(client = matrixClient)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, userDefinedMode) notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, userDefinedMode)
val updatedState = consumeItemsUntilPredicate { state -> val updatedState = consumeItemsUntilPredicate { state ->
(state.contentState as? RoomListContentState.Rooms)?.summaries.orEmpty().any { summary -> (state.contentState as? RoomListContentState.Rooms)?.summaries.orEmpty().any { summary ->
@ -394,13 +377,11 @@ class RoomListPresenterTest {
givenGetRoomResult(A_ROOM_ID, room) givenGetRoomResult(A_ROOM_ID, room)
} }
val presenter = createRoomListPresenter(client = client, analyticsService = analyticsService) val presenter = createRoomListPresenter(client = client, analyticsService = analyticsService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, true)) initialState.eventSink(RoomListEvent.SetRoomIsFavorite(A_ROOM_ID, true))
setIsFavoriteResult.assertions().isCalledOnce().with(value(true)) setIsFavoriteResult.assertions().isCalledOnce().with(value(true))
initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false)) initialState.eventSink(RoomListEvent.SetRoomIsFavorite(A_ROOM_ID, false))
setIsFavoriteResult.assertions().isCalledExactly(2) setIsFavoriteResult.assertions().isCalledExactly(2)
.withSequence( .withSequence(
listOf(value(true)), listOf(value(true)),
@ -424,9 +405,7 @@ class RoomListPresenterTest {
val presenter = createRoomListPresenter( val presenter = createRoomListPresenter(
client = matrixClient, client = matrixClient,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
assertThat(awaitItem().contentState).isInstanceOf(RoomListContentState.Empty::class.java) assertThat(awaitItem().contentState).isInstanceOf(RoomListContentState.Empty::class.java)
} }
} }
@ -463,23 +442,21 @@ class RoomListPresenterTest {
analyticsService = analyticsService, analyticsService = analyticsService,
notificationCleaner = notificationCleaner, notificationCleaner = notificationCleaner,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
allRooms.forEach { allRooms.forEach {
assertThat(it.setUnreadFlagCalls).isEmpty() assertThat(it.setUnreadFlagCalls).isEmpty()
} }
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID)) initialState.eventSink.invoke(RoomListEvent.MarkAsRead(A_ROOM_ID))
markAsReadResult.assertions().isCalledOnce().with(value(ReceiptType.READ)) markAsReadResult.assertions().isCalledOnce().with(value(ReceiptType.READ))
assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false)) assertThat(room.setUnreadFlagCalls).isEqualTo(listOf(false))
clearMessagesForRoomLambda.assertions().isCalledOnce() clearMessagesForRoomLambda.assertions().isCalledOnce()
.with(value(A_SESSION_ID), value(A_ROOM_ID)) .with(value(A_SESSION_ID), value(A_ROOM_ID))
initialState.eventSink.invoke(RoomListEvents.MarkAsUnread(A_ROOM_ID_2)) initialState.eventSink.invoke(RoomListEvent.MarkAsUnread(A_ROOM_ID_2))
assertThat(room2.setUnreadFlagCalls).isEqualTo(listOf(true)) assertThat(room2.setUnreadFlagCalls).isEqualTo(listOf(true))
// Test again with private read receipts // Test again with private read receipts
sessionPreferencesStore.setSendPublicReadReceipts(false) sessionPreferencesStore.setSendPublicReadReceipts(false)
initialState.eventSink.invoke(RoomListEvents.MarkAsRead(A_ROOM_ID_3)) initialState.eventSink.invoke(RoomListEvent.MarkAsRead(A_ROOM_ID_3))
markAsReadResult3.assertions().isCalledOnce().with(value(ReceiptType.READ_PRIVATE)) markAsReadResult3.assertions().isCalledOnce().with(value(ReceiptType.READ_PRIVATE))
assertThat(room3.setUnreadFlagCalls).isEqualTo(listOf(false)) assertThat(room3.setUnreadFlagCalls).isEqualTo(listOf(false))
clearMessagesForRoomLambda.assertions().isCalledExactly(2) clearMessagesForRoomLambda.assertions().isCalledExactly(2)
@ -525,8 +502,8 @@ class RoomListPresenterTest {
it.id == roomSummary.roomId.value it.id == roomSummary.roomId.value
} }
state.eventSink(RoomListEvents.AcceptInvite(roomListRoomSummary)) state.eventSink(RoomListEvent.AcceptInvite(roomListRoomSummary))
state.eventSink(RoomListEvents.DeclineInvite(roomListRoomSummary, blockUser = false)) state.eventSink(RoomListEvent.DeclineInvite(roomListRoomSummary, blockUser = false))
val inviteData = roomListRoomSummary.toInviteData() val inviteData = roomListRoomSummary.toInviteData()
assert(eventSinkRecorder) assert(eventSinkRecorder)
@ -559,9 +536,9 @@ class RoomListPresenterTest {
it.contentState is RoomListContentState.Rooms it.contentState is RoomListContentState.Rooms
}.last() }.last()
state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 10))) state.eventSink(RoomListEvent.UpdateVisibleRange(IntRange(0, 10)))
// If called again, it will cancel the current one, which should not result in a test failure // If called again, it will cancel the current one, which should not result in a test failure
state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 11))) state.eventSink(RoomListEvent.UpdateVisibleRange(IntRange(0, 11)))
advanceTimeBy(1.seconds) advanceTimeBy(1.seconds)
subscribeToVisibleRoomsLambda.assertions().isCalledOnce() subscribeToVisibleRoomsLambda.assertions().isCalledOnce()
} }
@ -588,12 +565,12 @@ class RoomListPresenterTest {
it.contentState is RoomListContentState.Rooms it.contentState is RoomListContentState.Rooms
}.last() }.last()
state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 10))) state.eventSink(RoomListEvent.UpdateVisibleRange(IntRange(0, 10)))
advanceTimeBy(1.seconds) advanceTimeBy(1.seconds)
subscribeToVisibleRoomsLambda.assertions().isCalledOnce() subscribeToVisibleRoomsLambda.assertions().isCalledOnce()
// If called again, it will subscribe to the next items // If called again, it will subscribe to the next items
state.eventSink(RoomListEvents.UpdateVisibleRange(IntRange(0, 11))) state.eventSink(RoomListEvent.UpdateVisibleRange(IntRange(0, 11)))
advanceTimeBy(1.seconds) advanceTimeBy(1.seconds)
subscribeToVisibleRoomsLambda.assertions().isCalledExactly(2) subscribeToVisibleRoomsLambda.assertions().isCalledExactly(2)
} }
@ -626,7 +603,7 @@ class RoomListPresenterTest {
assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse() assertThat(state.contentAsRooms().showNewNotificationSoundBanner).isFalse()
announcementService.emitAnnouncementsToShow(listOf(Announcement.NewNotificationSound)) announcementService.emitAnnouncementsToShow(listOf(Announcement.NewNotificationSound))
assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isTrue() assertThat(awaitItem().contentAsRooms().showNewNotificationSoundBanner).isTrue()
state.eventSink(RoomListEvents.DismissNewNotificationSoundBanner) state.eventSink(RoomListEvent.DismissNewNotificationSoundBanner)
onAnnouncementDismissedResult.assertions().isCalledOnce() onAnnouncementDismissedResult.assertions().isCalledOnce()
.with(value(Announcement.NewNotificationSound)) .with(value(Announcement.NewNotificationSound))
// Simulate service updating the value // Simulate service updating the value

View file

@ -46,7 +46,7 @@ class RoomListViewTest {
@Config(qualifiers = "h1024dp") @Config(qualifiers = "h1024dp")
@Test @Test
fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() { fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
rule.setRoomListView( rule.setRoomListView(
state = aRoomListState( state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation), contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
@ -56,15 +56,15 @@ class RoomListViewTest {
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListEvents.UpdateVisibleRange(IntRange.EMPTY), RoomListEvent.UpdateVisibleRange(IntRange.EMPTY),
RoomListEvents.UpdateVisibleRange(0..5), RoomListEvent.UpdateVisibleRange(0..5),
) )
) )
} }
@Test @Test
fun `clicking on close recovery key banner emits the expected Event`() { fun `clicking on close recovery key banner emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
rule.setRoomListView( rule.setRoomListView(
state = aRoomListState( state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation), contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
@ -77,12 +77,12 @@ class RoomListViewTest {
val close = rule.activity.getString(CommonStrings.action_close) val close = rule.activity.getString(CommonStrings.action_close)
rule.onNodeWithContentDescription(close).performClick() rule.onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(RoomListEvents.DismissBanner) eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
} }
@Test @Test
fun `clicking on close setup key banner emits the expected Event`() { fun `clicking on close setup key banner emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
rule.setRoomListView( rule.setRoomListView(
state = aRoomListState( state = aRoomListState(
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery), contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
@ -95,12 +95,12 @@ class RoomListViewTest {
val close = rule.activity.getString(CommonStrings.action_close) val close = rule.activity.getString(CommonStrings.action_close)
rule.onNodeWithContentDescription(close).performClick() rule.onNodeWithContentDescription(close).performClick()
eventsRecorder.assertSingle(RoomListEvents.DismissBanner) eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
} }
@Test @Test
fun `clicking on continue recovery key banner invokes the expected callback`() { fun `clicking on continue recovery key banner invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
rule.setRoomListView( rule.setRoomListView(
state = aRoomListState( state = aRoomListState(
@ -121,7 +121,7 @@ class RoomListViewTest {
@Test @Test
fun `clicking on continue setup key banner invokes the expected callback`() { fun `clicking on continue setup key banner invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
rule.setRoomListView( rule.setRoomListView(
state = aRoomListState( state = aRoomListState(
@ -139,7 +139,7 @@ class RoomListViewTest {
@Test @Test
fun `clicking on start chat when the session has no room invokes the expected callback`() { fun `clicking on start chat when the session has no room invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>(expectEvents = false) val eventsRecorder = EventsRecorder<RoomListEvent>(expectEvents = false)
ensureCalledOnce { callback -> ensureCalledOnce { callback ->
rule.setRoomListView( rule.setRoomListView(
state = aRoomListState( state = aRoomListState(
@ -154,7 +154,7 @@ class RoomListViewTest {
@Test @Test
fun `clicking on a room invokes the expected callback`() { fun `clicking on a room invokes the expected callback`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState( val state = aRoomListState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
) )
@ -178,7 +178,7 @@ class RoomListViewTest {
@Test @Test
fun `clicking on a room twice invokes the expected callback only once`() { fun `clicking on a room twice invokes the expected callback only once`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState( val state = aRoomListState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
) )
@ -201,7 +201,7 @@ class RoomListViewTest {
@Test @Test
fun `long clicking on a room emits the expected Event`() { fun `long clicking on a room emits the expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState( val state = aRoomListState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
) )
@ -215,12 +215,12 @@ class RoomListViewTest {
eventsRecorder.clear() eventsRecorder.clear()
rule.onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() } rule.onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() }
eventsRecorder.assertSingle(RoomListEvents.ShowContextMenu(room0)) eventsRecorder.assertSingle(RoomListEvent.ShowContextMenu(room0))
} }
@Test @Test
fun `clicking on a room setting invokes the expected callback and emits expected Event`() { fun `clicking on a room setting invokes the expected callback and emits expected Event`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState( val state = aRoomListState(
contextMenu = aContextMenuShown(), contextMenu = aContextMenuShown(),
eventSink = eventsRecorder, eventSink = eventsRecorder,
@ -238,12 +238,12 @@ class RoomListViewTest {
rule.clickOn(CommonStrings.common_settings) rule.clickOn(CommonStrings.common_settings)
} }
eventsRecorder.assertSingle(RoomListEvents.HideContextMenu) eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
} }
@Test @Test
fun `clicking on accept and decline invite emits the expected Events`() { fun `clicking on accept and decline invite emits the expected Events`() {
val eventsRecorder = EventsRecorder<RoomListEvents>() val eventsRecorder = EventsRecorder<RoomListEvent>()
val state = aRoomListState( val state = aRoomListState(
eventSink = eventsRecorder, eventSink = eventsRecorder,
) )
@ -259,8 +259,8 @@ class RoomListViewTest {
rule.clickOn(CommonStrings.action_decline) rule.clickOn(CommonStrings.action_decline)
eventsRecorder.assertList( eventsRecorder.assertList(
listOf( listOf(
RoomListEvents.AcceptInvite(invitedRoom), RoomListEvent.AcceptInvite(invitedRoom),
RoomListEvents.ShowDeclineInviteMenu(invitedRoom), RoomListEvent.ShowDeclineInviteMenu(invitedRoom),
) )
) )
} }

View file

@ -8,9 +8,6 @@
package io.element.android.features.home.impl.search package io.element.android.features.home.impl.search
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.home.impl.datasource.aRoomListRoomSummaryFactory import io.element.android.features.home.impl.datasource.aRoomListRoomSummaryFactory
import io.element.android.libraries.dateformatter.test.FakeDateFormatter import io.element.android.libraries.dateformatter.test.FakeDateFormatter
@ -19,6 +16,7 @@ import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.test.room.aRoomSummary import io.element.android.libraries.matrix.test.room.aRoomSummary
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.tests.testutils.test
import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.TestScope
@ -29,9 +27,7 @@ class RoomListSearchPresenterTest {
@Test @Test
fun `present - initial state`() = runTest { fun `present - initial state`() = runTest {
val presenter = createRoomListSearchPresenter() val presenter = createRoomListSearchPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
awaitItem().let { state -> awaitItem().let { state ->
assertThat(state.isSearchActive).isFalse() assertThat(state.isSearchActive).isFalse()
assertThat(state.query.text.toString()).isEmpty() assertThat(state.query.text.toString()).isEmpty()
@ -43,16 +39,14 @@ class RoomListSearchPresenterTest {
@Test @Test
fun `present - toggle search visibility`() = runTest { fun `present - toggle search visibility`() = runTest {
val presenter = createRoomListSearchPresenter() val presenter = createRoomListSearchPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
awaitItem().let { state -> awaitItem().let { state ->
assertThat(state.isSearchActive).isFalse() assertThat(state.isSearchActive).isFalse()
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility) state.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
} }
awaitItem().let { state -> awaitItem().let { state ->
assertThat(state.isSearchActive).isTrue() assertThat(state.isSearchActive).isTrue()
state.eventSink(RoomListSearchEvents.ToggleSearchVisibility) state.eventSink(RoomListSearchEvent.ToggleSearchVisibility)
} }
awaitItem().let { state -> awaitItem().let { state ->
assertThat(state.isSearchActive).isFalse() assertThat(state.isSearchActive).isFalse()
@ -64,9 +58,7 @@ class RoomListSearchPresenterTest {
fun `present - query search changes`() = runTest { fun `present - query search changes`() = runTest {
val roomListService = FakeRoomListService() val roomListService = FakeRoomListService()
val presenter = createRoomListSearchPresenter(roomListService) val presenter = createRoomListSearchPresenter(roomListService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
awaitItem().let { state -> awaitItem().let { state ->
assertThat( assertThat(
roomListService.allRooms.currentFilter.value roomListService.allRooms.currentFilter.value
@ -82,7 +74,7 @@ class RoomListSearchPresenterTest {
).isEqualTo( ).isEqualTo(
RoomListFilter.NormalizedMatchRoomName("Search") RoomListFilter.NormalizedMatchRoomName("Search")
) )
state.eventSink(RoomListSearchEvents.ClearQuery) state.eventSink(RoomListSearchEvent.ClearQuery)
} }
awaitItem().let { state -> awaitItem().let { state ->
assertThat(state.query.text.toString()).isEmpty() assertThat(state.query.text.toString()).isEmpty()
@ -99,9 +91,7 @@ class RoomListSearchPresenterTest {
fun `present - room list changes`() = runTest { fun `present - room list changes`() = runTest {
val roomListService = FakeRoomListService() val roomListService = FakeRoomListService()
val presenter = createRoomListSearchPresenter(roomListService) val presenter = createRoomListSearchPresenter(roomListService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
awaitItem().let { state -> awaitItem().let { state ->
assertThat(state.results).isEmpty() assertThat(state.results).isEmpty()
} }

View file

@ -589,9 +589,14 @@ private fun FavoriteItem(
isFavorite: Boolean, isFavorite: Boolean,
onFavoriteChanges: (Boolean) -> Unit, onFavoriteChanges: (Boolean) -> Unit,
) { ) {
val (textResId, icon) = if (isFavorite) {
CommonStrings.common_favourited to CompoundIcons.FavouriteSolid()
} else {
CommonStrings.common_favourite to CompoundIcons.Favourite()
}
PreferenceSwitch( PreferenceSwitch(
icon = CompoundIcons.Favourite(), icon = icon,
title = stringResource(id = CommonStrings.common_favourite), title = stringResource(id = textResId),
isChecked = isFavorite, isChecked = isFavorite,
onCheckedChange = onFavoriteChanges onCheckedChange = onFavoriteChanges
) )

View file

@ -10,7 +10,7 @@ package io.element.android.features.roomdetails.impl.members
import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.api.room.RoomMember
sealed interface RoomMemberListEvents { sealed interface RoomMemberListEvent {
data class ChangeSelectedSection(val section: SelectedSection) : RoomMemberListEvents data class ChangeSelectedSection(val section: SelectedSection) : RoomMemberListEvent
data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvents data class RoomMemberSelected(val roomMember: RoomMember) : RoomMemberListEvent
} }

View file

@ -125,11 +125,11 @@ class RoomMemberListPresenter(
} }
} }
fun handleEvent(event: RoomMemberListEvents) { fun handleEvent(event: RoomMemberListEvent) {
when (event) { when (event) {
is RoomMemberListEvents.RoomMemberSelected -> is RoomMemberListEvent.RoomMemberSelected ->
roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser())) roomModerationState.eventSink(ShowActionsForUser(event.roomMember.toMatrixUser()))
is RoomMemberListEvents.ChangeSelectedSection -> selectedSection = event.section is RoomMemberListEvent.ChangeSelectedSection -> selectedSection = event.section
} }
} }

View file

@ -25,7 +25,7 @@ data class RoomMemberListState(
val canInvite: Boolean, val canInvite: Boolean,
val selectedSection: SelectedSection, val selectedSection: SelectedSection,
val moderationState: RoomMemberModerationState, val moderationState: RoomMemberModerationState,
val eventSink: (RoomMemberListEvents) -> Unit, val eventSink: (RoomMemberListEvent) -> Unit,
) { ) {
val showBannedSection: Boolean = moderationState.permissions.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true val showBannedSection: Boolean = moderationState.permissions.canBan && roomMembers.dataOrNull()?.banned?.isNotEmpty() == true
} }

View file

@ -85,7 +85,7 @@ internal fun aRoomMemberListState(
selectedSection: SelectedSection = SelectedSection.MEMBERS, selectedSection: SelectedSection = SelectedSection.MEMBERS,
searchQuery: String = "", searchQuery: String = "",
canInvite: Boolean = false, canInvite: Boolean = false,
eventSink: (RoomMemberListEvents) -> Unit = {}, eventSink: (RoomMemberListEvent) -> Unit = {},
) = RoomMemberListState( ) = RoomMemberListState(
roomMembers = roomMembers, roomMembers = roomMembers,
filteredRoomMembers = roomMembers.map { it.filter(searchQuery) }, filteredRoomMembers = roomMembers.map { it.filter(searchQuery) },

View file

@ -25,8 +25,6 @@ import androidx.compose.foundation.lazy.rememberLazyListState
import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.res.pluralStringResource import androidx.compose.ui.res.pluralStringResource
@ -68,7 +66,7 @@ fun RoomMemberListView(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
) { ) {
fun onSelectUser(roomMember: RoomMember) { fun onSelectUser(roomMember: RoomMember) {
state.eventSink(RoomMemberListEvents.RoomMemberSelected(roomMember)) state.eventSink(RoomMemberListEvent.RoomMemberSelected(roomMember))
} }
Scaffold( Scaffold(
@ -100,7 +98,7 @@ fun RoomMemberListView(
selectedSection = state.selectedSection, selectedSection = state.selectedSection,
showBannedSection = state.showBannedSection, showBannedSection = state.showBannedSection,
searchQuery = state.searchQuery.text.toString(), searchQuery = state.searchQuery.text.toString(),
onSelectedSectionChange = { state.eventSink(RoomMemberListEvents.ChangeSelectedSection(it)) }, onSelectedSectionChange = { state.eventSink(RoomMemberListEvent.ChangeSelectedSection(it)) },
onSelectUser = ::onSelectUser, onSelectUser = ::onSelectUser,
) )
} }

View file

@ -10,10 +10,10 @@ package io.element.android.features.roomdetails.impl.notificationsettings
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
sealed interface RoomNotificationSettingsEvents { sealed interface RoomNotificationSettingsEvent {
data class ChangeRoomNotificationMode(val mode: RoomNotificationMode) : RoomNotificationSettingsEvents data class ChangeRoomNotificationMode(val mode: RoomNotificationMode) : RoomNotificationSettingsEvent
data class SetNotificationMode(val isDefault: Boolean) : RoomNotificationSettingsEvents data class SetNotificationMode(val isDefault: Boolean) : RoomNotificationSettingsEvent
data object DeleteCustomNotification : RoomNotificationSettingsEvents data object DeleteCustomNotification : RoomNotificationSettingsEvent
data object ClearSetNotificationError : RoomNotificationSettingsEvents data object ClearSetNotificationError : RoomNotificationSettingsEvent
data object ClearRestoreDefaultError : RoomNotificationSettingsEvents data object ClearRestoreDefaultError : RoomNotificationSettingsEvent
} }

View file

@ -100,12 +100,12 @@ class RoomNotificationSettingsPresenter(
!notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true) !notificationSettingsService.canHomeServerPushEncryptedEventsToDevice().getOrDefault(true)
} }
fun handleEvent(event: RoomNotificationSettingsEvents) { fun handleEvent(event: RoomNotificationSettingsEvent) {
when (event) { when (event) {
is RoomNotificationSettingsEvents.ChangeRoomNotificationMode -> { is RoomNotificationSettingsEvent.ChangeRoomNotificationMode -> {
localCoroutineScope.setRoomNotificationMode(event.mode, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction) localCoroutineScope.setRoomNotificationMode(event.mode, pendingRoomNotificationMode, pendingSetDefault, setNotificationSettingAction)
} }
is RoomNotificationSettingsEvents.SetNotificationMode -> { is RoomNotificationSettingsEvent.SetNotificationMode -> {
if (event.isDefault) { if (event.isDefault) {
localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault) localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault)
} else { } else {
@ -114,13 +114,13 @@ class RoomNotificationSettingsPresenter(
} }
} }
} }
is RoomNotificationSettingsEvents.DeleteCustomNotification -> { is RoomNotificationSettingsEvent.DeleteCustomNotification -> {
localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault) localCoroutineScope.restoreDefaultRoomNotificationMode(restoreDefaultAction, pendingSetDefault)
} }
RoomNotificationSettingsEvents.ClearSetNotificationError -> { RoomNotificationSettingsEvent.ClearSetNotificationError -> {
setNotificationSettingAction.value = AsyncAction.Uninitialized setNotificationSettingAction.value = AsyncAction.Uninitialized
} }
RoomNotificationSettingsEvents.ClearRestoreDefaultError -> { RoomNotificationSettingsEvent.ClearRestoreDefaultError -> {
restoreDefaultAction.value = AsyncAction.Uninitialized restoreDefaultAction.value = AsyncAction.Uninitialized
} }
} }

View file

@ -23,7 +23,7 @@ data class RoomNotificationSettingsState(
val setNotificationSettingAction: AsyncAction<Unit>, val setNotificationSettingAction: AsyncAction<Unit>,
val restoreDefaultAction: AsyncAction<Unit>, val restoreDefaultAction: AsyncAction<Unit>,
val displayMentionsOnlyDisclaimer: Boolean, val displayMentionsOnlyDisclaimer: Boolean,
val eventSink: (RoomNotificationSettingsEvents) -> Unit val eventSink: (RoomNotificationSettingsEvent) -> Unit
) )
val RoomNotificationSettingsState.displayNotificationMode: RoomNotificationMode? get() { val RoomNotificationSettingsState.displayNotificationMode: RoomNotificationMode? get() {

View file

@ -85,7 +85,7 @@ private fun RoomSpecificNotificationSettingsView(
PreferenceSwitch( PreferenceSwitch(
isChecked = !state.displayIsDefault.orTrue(), isChecked = !state.displayIsDefault.orTrue(),
onCheckedChange = { onCheckedChange = {
state.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(!it)) state.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(!it))
}, },
title = stringResource(id = R.string.screen_room_notification_settings_allow_custom), title = stringResource(id = R.string.screen_room_notification_settings_allow_custom),
subtitle = stringResource(id = R.string.screen_room_notification_settings_allow_custom_footnote), subtitle = stringResource(id = R.string.screen_room_notification_settings_allow_custom_footnote),
@ -138,7 +138,7 @@ private fun RoomSpecificNotificationSettingsView(
enabled = !state.displayIsDefault.orTrue(), enabled = !state.displayIsDefault.orTrue(),
displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer, displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer,
onSelectOption = { onSelectOption = {
state.eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(it.mode)) state.eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(it.mode))
}, },
) )
} }
@ -148,14 +148,14 @@ private fun RoomSpecificNotificationSettingsView(
async = state.setNotificationSettingAction, async = state.setNotificationSettingAction,
onSuccess = {}, onSuccess = {},
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvent.ClearSetNotificationError) },
) )
AsyncActionView( AsyncActionView(
async = state.restoreDefaultAction, async = state.restoreDefaultAction,
onSuccess = {}, onSuccess = {},
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvent.ClearRestoreDefaultError) },
) )
} }
} }

View file

@ -60,7 +60,7 @@ fun UserDefinedRoomNotificationSettingsView(
enabled = !state.displayIsDefault.orTrue(), enabled = !state.displayIsDefault.orTrue(),
displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer, displayMentionsOnlyDisclaimer = state.displayMentionsOnlyDisclaimer,
onSelectOption = { onSelectOption = {
state.eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(it.mode)) state.eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(it.mode))
}, },
) )
} }
@ -69,7 +69,7 @@ fun UserDefinedRoomNotificationSettingsView(
headlineContent = { Text(stringResource(R.string.screen_room_notification_settings_edit_remove_setting)) }, headlineContent = { Text(stringResource(R.string.screen_room_notification_settings_edit_remove_setting)) },
style = ListItemStyle.Destructive, style = ListItemStyle.Destructive,
onClick = { onClick = {
state.eventSink(RoomNotificationSettingsEvents.DeleteCustomNotification) state.eventSink(RoomNotificationSettingsEvent.DeleteCustomNotification)
} }
) )
@ -77,14 +77,14 @@ fun UserDefinedRoomNotificationSettingsView(
async = state.setNotificationSettingAction, async = state.setNotificationSettingAction,
onSuccess = {}, onSuccess = {},
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvent.ClearSetNotificationError) },
) )
AsyncActionView( AsyncActionView(
async = state.restoreDefaultAction, async = state.restoreDefaultAction,
onSuccess = { onBackClick() }, onSuccess = { onBackClick() },
errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) }, errorMessage = { stringResource(R.string.screen_notification_settings_edit_failed_updating_default_mode) },
onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) }, onErrorDismiss = { state.eventSink(RoomNotificationSettingsEvent.ClearRestoreDefaultError) },
) )
} }
} }

View file

@ -66,7 +66,7 @@ class RoomMemberListPresenterTest {
skipItems(1) skipItems(1)
val loadedState = awaitItem() val loadedState = awaitItem()
assertThat(loadedState.showBannedSection).isTrue() assertThat(loadedState.showBannedSection).isTrue()
loadedState.eventSink(RoomMemberListEvents.ChangeSelectedSection(SelectedSection.BANNED)) loadedState.eventSink(RoomMemberListEvent.ChangeSelectedSection(SelectedSection.BANNED))
val bannedSectionState = awaitItem() val bannedSectionState = awaitItem()
assertThat(bannedSectionState.selectedSection).isEqualTo(SelectedSection.BANNED) assertThat(bannedSectionState.selectedSection).isEqualTo(SelectedSection.BANNED)
// Now update the room members to have no banned users // Now update the room members to have no banned users
@ -188,7 +188,7 @@ class RoomMemberListPresenterTest {
) )
presenter.test { presenter.test {
skipItems(1) skipItems(1)
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(anInvitedVictor())) awaitItem().eventSink(RoomMemberListEvent.RoomMemberSelected(anInvitedVictor()))
} }
} }
} }

View file

@ -8,9 +8,6 @@
package io.element.android.features.roomdetails.impl.members.details package io.element.android.features.roomdetails.impl.members.details
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.roomdetails.impl.aJoinedRoom import io.element.android.features.roomdetails.impl.aJoinedRoom
import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.aRoomMember
@ -89,9 +86,7 @@ class RoomMemberDetailsPresenterTest {
val presenter = createRoomMemberDetailsPresenter( val presenter = createRoomMemberDetailsPresenter(
room = room, room = room,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.userName).isEqualTo("Alice") assertThat(initialState.userName).isEqualTo("Alice")
assertThat(initialState.avatarUrl).isEqualTo("Alice Avatar url") assertThat(initialState.avatarUrl).isEqualTo("Alice Avatar url")
@ -111,9 +106,7 @@ class RoomMemberDetailsPresenterTest {
val presenter = createRoomMemberDetailsPresenter( val presenter = createRoomMemberDetailsPresenter(
room = room, room = room,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.userName).isEqualTo("Alice") assertThat(initialState.userName).isEqualTo("Alice")
assertThat(initialState.avatarUrl).isEqualTo("Profile avatar url") assertThat(initialState.avatarUrl).isEqualTo("Profile avatar url")
@ -130,9 +123,7 @@ class RoomMemberDetailsPresenterTest {
val presenter = createRoomMemberDetailsPresenter( val presenter = createRoomMemberDetailsPresenter(
room = room, room = room,
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.userName).isEqualTo("Profile user name") assertThat(initialState.userName).isEqualTo("Profile user name")
assertThat(initialState.avatarUrl).isEqualTo("Profile avatar url") assertThat(initialState.avatarUrl).isEqualTo("Profile avatar url")
@ -161,9 +152,7 @@ class RoomMemberDetailsPresenterTest {
} }
}, },
) )
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.userName).isNull() assertThat(initialState.userName).isNull()
assertThat(initialState.avatarUrl).isNull() assertThat(initialState.avatarUrl).isNull()

View file

@ -8,9 +8,6 @@
package io.element.android.features.roomdetails.impl.notificationsettings package io.element.android.features.roomdetails.impl.notificationsettings
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.roomdetails.impl.aJoinedRoom import io.element.android.features.roomdetails.impl.aJoinedRoom
import io.element.android.libraries.matrix.api.room.RoomNotificationMode import io.element.android.libraries.matrix.api.room.RoomNotificationMode
@ -20,6 +17,7 @@ import io.element.android.libraries.matrix.test.notificationsettings.FakeNotific
import io.element.android.libraries.matrix.test.room.FakeJoinedRoom import io.element.android.libraries.matrix.test.room.FakeJoinedRoom
import io.element.android.tests.testutils.awaitLastSequentialItem import io.element.android.tests.testutils.awaitLastSequentialItem
import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.test
import kotlinx.coroutines.test.runTest import kotlinx.coroutines.test.runTest
import org.junit.Test import org.junit.Test
@ -27,9 +25,7 @@ class RoomNotificationSettingsPresenterTest {
@Test @Test
fun `present - initial state is created from room info`() = runTest { fun `present - initial state is created from room info`() = runTest {
val presenter = createRoomNotificationSettingsPresenter() val presenter = createRoomNotificationSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
assertThat(initialState.roomNotificationSettings.dataOrNull()).isNull() assertThat(initialState.roomNotificationSettings.dataOrNull()).isNull()
assertThat(initialState.defaultRoomNotificationMode).isNull() assertThat(initialState.defaultRoomNotificationMode).isNull()
@ -42,10 +38,8 @@ class RoomNotificationSettingsPresenterTest {
@Test @Test
fun `present - notification mode changed`() = runTest { fun `present - notification mode changed`() = runTest {
val presenter = createRoomNotificationSettingsPresenter() val presenter = createRoomNotificationSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present() awaitItem().eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
}.test {
awaitItem().eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
val updatedState = consumeItemsUntilPredicate { val updatedState = consumeItemsUntilPredicate {
it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
}.last() }.last()
@ -58,9 +52,7 @@ class RoomNotificationSettingsPresenterTest {
fun `present - observe notification mode changed`() = runTest { fun `present - observe notification mode changed`() = runTest {
val notificationSettingsService = FakeNotificationSettingsService() val notificationSettingsService = FakeNotificationSettingsService()
val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) notificationSettingsService.setRoomNotificationMode(A_ROOM_ID, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)
val updatedState = consumeItemsUntilPredicate { val updatedState = consumeItemsUntilPredicate {
it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
@ -74,11 +66,9 @@ class RoomNotificationSettingsPresenterTest {
val notificationSettingsService = FakeNotificationSettingsService() val notificationSettingsService = FakeNotificationSettingsService()
notificationSettingsService.givenSetNotificationModeError(AN_EXCEPTION) notificationSettingsService.givenSetNotificationModeError(AN_EXCEPTION)
val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false)) initialState.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(false))
val failedState = consumeItemsUntilPredicate { val failedState = consumeItemsUntilPredicate {
it.setNotificationSettingAction.isFailure() it.setNotificationSettingAction.isFailure()
}.last() }.last()
@ -87,7 +77,7 @@ class RoomNotificationSettingsPresenterTest {
assertThat(failedState.pendingSetDefault).isNull() assertThat(failedState.pendingSetDefault).isNull()
assertThat(failedState.setNotificationSettingAction.isFailure()).isTrue() assertThat(failedState.setNotificationSettingAction.isFailure()).isTrue()
failedState.eventSink(RoomNotificationSettingsEvents.ClearSetNotificationError) failedState.eventSink(RoomNotificationSettingsEvent.ClearSetNotificationError)
val errorClearedState = consumeItemsUntilPredicate { val errorClearedState = consumeItemsUntilPredicate {
it.setNotificationSettingAction.isUninitialized() it.setNotificationSettingAction.isUninitialized()
@ -100,11 +90,9 @@ class RoomNotificationSettingsPresenterTest {
fun `present - notification settings set custom`() = runTest { fun `present - notification settings set custom`() = runTest {
val notificationSettingsService = FakeNotificationSettingsService() val notificationSettingsService = FakeNotificationSettingsService()
val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(false)) initialState.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(false))
skipItems(3) skipItems(3)
val defaultState = awaitItem() val defaultState = awaitItem()
assertThat(defaultState.roomNotificationSettings.dataOrNull()?.isDefault).isFalse() assertThat(defaultState.roomNotificationSettings.dataOrNull()?.isDefault).isFalse()
@ -115,12 +103,10 @@ class RoomNotificationSettingsPresenterTest {
@Test @Test
fun `present - notification settings restore default`() = runTest { fun `present - notification settings restore default`() = runTest {
val presenter = createRoomNotificationSettingsPresenter() val presenter = createRoomNotificationSettingsPresenter()
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) initialState.eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) initialState.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(true))
val defaultState = consumeItemsUntilPredicate { val defaultState = consumeItemsUntilPredicate {
it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY it.roomNotificationSettings.dataOrNull()?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
}.last() }.last()
@ -134,17 +120,15 @@ class RoomNotificationSettingsPresenterTest {
val notificationSettingsService = FakeNotificationSettingsService() val notificationSettingsService = FakeNotificationSettingsService()
notificationSettingsService.givenRestoreDefaultNotificationModeError(AN_EXCEPTION) notificationSettingsService.givenRestoreDefaultNotificationModeError(AN_EXCEPTION)
val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService) val presenter = createRoomNotificationSettingsPresenter(notificationSettingsService)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
val initialState = awaitItem() val initialState = awaitItem()
initialState.eventSink(RoomNotificationSettingsEvents.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY)) initialState.eventSink(RoomNotificationSettingsEvent.ChangeRoomNotificationMode(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY))
initialState.eventSink(RoomNotificationSettingsEvents.SetNotificationMode(true)) initialState.eventSink(RoomNotificationSettingsEvent.SetNotificationMode(true))
val failedState = consumeItemsUntilPredicate { val failedState = consumeItemsUntilPredicate {
it.restoreDefaultAction.isFailure() it.restoreDefaultAction.isFailure()
}.last() }.last()
assertThat(failedState.restoreDefaultAction.isFailure()).isTrue() assertThat(failedState.restoreDefaultAction.isFailure()).isTrue()
failedState.eventSink(RoomNotificationSettingsEvents.ClearRestoreDefaultError) failedState.eventSink(RoomNotificationSettingsEvent.ClearRestoreDefaultError)
val errorClearedState = consumeItemsUntilPredicate { val errorClearedState = consumeItemsUntilPredicate {
it.restoreDefaultAction.isUninitialized() it.restoreDefaultAction.isUninitialized()
@ -161,9 +145,7 @@ class RoomNotificationSettingsPresenterTest {
} }
val room = aJoinedRoom(notificationSettingsService = notificationService, isEncrypted = true) val room = aJoinedRoom(notificationSettingsService = notificationService, isEncrypted = true)
val presenter = createRoomNotificationSettingsPresenter(notificationService, room) val presenter = createRoomNotificationSettingsPresenter(notificationService, room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
assertThat(awaitLastSequentialItem().displayMentionsOnlyDisclaimer).isTrue() assertThat(awaitLastSequentialItem().displayMentionsOnlyDisclaimer).isTrue()
} }
} }
@ -175,9 +157,7 @@ class RoomNotificationSettingsPresenterTest {
} }
val room = aJoinedRoom(notificationSettingsService = notificationService, isEncrypted = false) val room = aJoinedRoom(notificationSettingsService = notificationService, isEncrypted = false)
val presenter = createRoomNotificationSettingsPresenter(notificationService, room) val presenter = createRoomNotificationSettingsPresenter(notificationService, room)
moleculeFlow(RecompositionMode.Immediate) { presenter.test {
presenter.present()
}.test {
assertThat(awaitLastSequentialItem().displayMentionsOnlyDisclaimer).isFalse() assertThat(awaitLastSequentialItem().displayMentionsOnlyDisclaimer).isFalse()
} }
} }

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:cb454d3b19178dc9c58248e350897037878b7a7b53786856658dcca2c9c726cc oid sha256:e0374001ca9bf5f62164fdeb10647d696f91da54fa39d34803a2ac2e3913f813
size 22831 size 22490

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:e2353f91c964b2d29f658d2847b3cd344c3c0466dbc26324f0f8e1ad0e0eb6bb oid sha256:72282614c61dded4307422180cedd2069da198d0c538cd6722d88712ec198b49
size 21939 size 21428

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:26bfe68b4448aa83e19f196d8633eeb74883064db319aab355a5e755b7cbb56d oid sha256:50e8fedd31aaa5dbdb63e088709a5e237f8c5400ca7209390d837a07d9b86973
size 52439 size 52404

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:aba9a16cabbe67f28512721740e268bebd93b3084efd93e8de7eed251fd1ffe0 oid sha256:6a83be8296a2abd4a2bd28f08c8687cac1dddaaedce4ccf784538892da7b069c
size 49849 size 49831

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:eb28bb52f2525e56a9878a15f4ead5082552f34c949478e9007daa99b11672bd oid sha256:1ba8705491ab83f77721ca341784ccac456b158a6e4f3ae194226ace89771088
size 42242 size 42234

View file

@ -1,3 +1,3 @@
version https://git-lfs.github.com/spec/v1 version https://git-lfs.github.com/spec/v1
oid sha256:4deed8665be752e542a35fa10fbfe52912c439989ae021ac8234f5641a6e952b oid sha256:e418227fc3df1b8a5fc2b4347ed37bb842995b80407c22fcea9cc075e931724e
size 43284 size 43294