Add tests for SpaceFiltersPresenter and SpaceFiltersView and fix quality
This commit is contained in:
parent
354e126a96
commit
36fb3e251d
12 changed files with 345 additions and 53 deletions
|
|
@ -32,7 +32,7 @@ data class HomeState(
|
||||||
val directLogoutState: DirectLogoutState,
|
val directLogoutState: DirectLogoutState,
|
||||||
val eventSink: (HomeEvent) -> Unit,
|
val eventSink: (HomeEvent) -> Unit,
|
||||||
) {
|
) {
|
||||||
val isBackHandlerEnabled = currentHomeNavigationBarItem != HomeNavigationBarItem.Chats || roomListState.spaceFiltersState is SpaceFiltersState.Selected
|
val isBackHandlerEnabled = currentHomeNavigationBarItem != HomeNavigationBarItem.Chats || roomListState.spaceFiltersState is SpaceFiltersState.Selected
|
||||||
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
|
val displayActions = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats
|
||||||
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters
|
val displayRoomListFilters = currentHomeNavigationBarItem == HomeNavigationBarItem.Chats && roomListState.displayFilters
|
||||||
val showNavigationBar = homeSpacesState.canCreateSpaces || homeSpacesState.spaceRooms.isNotEmpty()
|
val showNavigationBar = homeSpacesState.canCreateSpaces || homeSpacesState.spaceRooms.isNotEmpty()
|
||||||
|
|
|
||||||
|
|
@ -375,6 +375,29 @@ internal fun HomeTopBarPreview() = ElementPreview {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
@PreviewsDayNight
|
||||||
|
@Composable
|
||||||
|
internal fun HomeTopBarSpaceFiltersSelectedPreview() = ElementPreview {
|
||||||
|
HomeTopBar(
|
||||||
|
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
||||||
|
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
||||||
|
showAvatarIndicator = false,
|
||||||
|
areSearchResultsDisplayed = false,
|
||||||
|
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
|
||||||
|
onOpenSettings = {},
|
||||||
|
onAccountSwitch = {},
|
||||||
|
onToggleSearch = {},
|
||||||
|
onCreateSpace = {},
|
||||||
|
canCreateSpaces = true,
|
||||||
|
canReportBug = true,
|
||||||
|
displayFilters = true,
|
||||||
|
filtersState = aRoomListFiltersState(),
|
||||||
|
spaceFiltersState = aSelectedSpaceFiltersState(),
|
||||||
|
onMenuActionClick = {},
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -443,26 +466,3 @@ internal fun HomeTopBarMultiAccountPreview() = ElementPreview {
|
||||||
onMenuActionClick = {},
|
onMenuActionClick = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
|
||||||
@PreviewsDayNight
|
|
||||||
@Composable
|
|
||||||
internal fun HomeTopSpaceFiltersSelectedPreview() = ElementPreview {
|
|
||||||
HomeTopBar(
|
|
||||||
selectedNavigationItem = HomeNavigationBarItem.Chats,
|
|
||||||
currentUserAndNeighbors = persistentListOf(MatrixUser(UserId("@id:domain"), "Alice")),
|
|
||||||
showAvatarIndicator = false,
|
|
||||||
areSearchResultsDisplayed = false,
|
|
||||||
scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(rememberTopAppBarState()),
|
|
||||||
onOpenSettings = {},
|
|
||||||
onAccountSwitch = {},
|
|
||||||
onToggleSearch = {},
|
|
||||||
onCreateSpace = {},
|
|
||||||
canCreateSpaces = true,
|
|
||||||
canReportBug = true,
|
|
||||||
displayFilters = true,
|
|
||||||
filtersState = aRoomListFiltersState(),
|
|
||||||
spaceFiltersState = aSelectedSpaceFiltersState(),
|
|
||||||
onMenuActionClick = {},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,8 @@ import io.element.android.features.home.impl.roomlist.RoomListPresenter
|
||||||
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.RoomListSearchPresenter
|
import io.element.android.features.home.impl.search.RoomListSearchPresenter
|
||||||
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.spacefilters.SpaceFiltersPresenter
|
||||||
|
import io.element.android.features.home.impl.spacefilters.SpaceFiltersState
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.di.SessionScope
|
import io.element.android.libraries.di.SessionScope
|
||||||
|
|
||||||
|
|
@ -31,4 +33,7 @@ interface RoomListModule {
|
||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
|
fun bindFiltersPresenter(presenter: RoomListFiltersPresenter): Presenter<RoomListFiltersState>
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
fun bindSpaceFiltersPresenter(presenter: SpaceFiltersPresenter): Presenter<SpaceFiltersState>
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -28,9 +28,7 @@ import im.vector.app.features.analytics.plan.Interaction
|
||||||
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
|
||||||
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.RoomListFilter.People
|
|
||||||
import io.element.android.features.home.impl.filters.RoomListFilter.Rooms
|
import io.element.android.features.home.impl.filters.RoomListFilter.Rooms
|
||||||
import io.element.android.features.home.impl.filters.RoomListFiltersEvent
|
|
||||||
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.into
|
import io.element.android.features.home.impl.filters.into
|
||||||
import io.element.android.features.home.impl.search.RoomListSearchEvent
|
import io.element.android.features.home.impl.search.RoomListSearchEvent
|
||||||
|
|
|
||||||
|
|
@ -15,9 +15,8 @@ import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
import io.element.android.libraries.di.SessionScope
|
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
import io.element.android.libraries.matrix.api.MatrixClient
|
import io.element.android.libraries.matrix.api.MatrixClient
|
||||||
|
|
@ -26,7 +25,7 @@ import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import kotlinx.coroutines.flow.map
|
import kotlinx.coroutines.flow.map
|
||||||
|
|
||||||
@ContributesBinding(SessionScope::class)
|
@Inject
|
||||||
class SpaceFiltersPresenter(
|
class SpaceFiltersPresenter(
|
||||||
private val featureFlagService: FeatureFlagService,
|
private val featureFlagService: FeatureFlagService,
|
||||||
private val matrixClient: MatrixClient,
|
private val matrixClient: MatrixClient,
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,13 @@
|
||||||
package io.element.android.features.home.impl.spacefilters
|
package io.element.android.features.home.impl.spacefilters
|
||||||
|
|
||||||
import androidx.compose.foundation.text.input.TextFieldState
|
import androidx.compose.foundation.text.input.TextFieldState
|
||||||
|
import androidx.compose.runtime.Immutable
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
|
||||||
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
import io.element.android.libraries.matrix.api.spaces.SpaceServiceFilter
|
||||||
import kotlinx.collections.immutable.ImmutableList
|
import kotlinx.collections.immutable.ImmutableList
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
|
|
||||||
|
@Immutable
|
||||||
sealed interface SpaceFiltersState {
|
sealed interface SpaceFiltersState {
|
||||||
data object Disabled : SpaceFiltersState
|
data object Disabled : SpaceFiltersState
|
||||||
|
|
||||||
|
|
@ -52,4 +54,3 @@ fun SpaceFiltersState.selectedFilter(): SpaceServiceFilter? {
|
||||||
fun SpaceServiceFilter?.into(): RoomListFilter? {
|
fun SpaceServiceFilter?.into(): RoomListFilter? {
|
||||||
return this?.let { RoomListFilter.Identifiers(descendants) }
|
return this?.let { RoomListFilter.Identifiers(descendants) }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,14 +11,6 @@ package io.element.android.features.home.impl.filters
|
||||||
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.dateformatter.api.DateFormatter
|
|
||||||
import io.element.android.libraries.dateformatter.test.FakeDateFormatter
|
|
||||||
import io.element.android.libraries.eventformatter.api.RoomLatestEventFormatter
|
|
||||||
import io.element.android.libraries.eventformatter.test.FakeRoomLatestEventFormatter
|
|
||||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
|
||||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
|
||||||
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
|
|
||||||
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 io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
|
@ -49,8 +41,7 @@ class RoomListFiltersPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun `present - toggle rooms filter`() = runTest {
|
fun `present - toggle rooms filter`() = runTest {
|
||||||
val roomListService = FakeRoomListService()
|
val presenter = createRoomListFiltersPresenter()
|
||||||
val presenter = createRoomListFiltersPresenter(roomListService)
|
|
||||||
presenter.test {
|
presenter.test {
|
||||||
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
||||||
awaitLastSequentialItem().let { state ->
|
awaitLastSequentialItem().let { state ->
|
||||||
|
|
@ -84,8 +75,7 @@ class RoomListFiltersPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
fun `present - clear filters event`() = runTest {
|
fun `present - clear filters event`() = runTest {
|
||||||
val roomListService = FakeRoomListService()
|
val presenter = createRoomListFiltersPresenter()
|
||||||
val presenter = createRoomListFiltersPresenter(roomListService)
|
|
||||||
presenter.test {
|
presenter.test {
|
||||||
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
awaitItem().eventSink.invoke(RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms))
|
||||||
awaitLastSequentialItem().let { state ->
|
awaitLastSequentialItem().let { state ->
|
||||||
|
|
@ -105,12 +95,7 @@ private fun filterSelectionState(filter: RoomListFilter, selected: Boolean) = Fi
|
||||||
isSelected = selected,
|
isSelected = selected,
|
||||||
)
|
)
|
||||||
|
|
||||||
private fun TestScope.createRoomListFiltersPresenter(
|
private fun TestScope.createRoomListFiltersPresenter(): RoomListFiltersPresenter {
|
||||||
roomListService: RoomListService = FakeRoomListService(),
|
|
||||||
notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
|
|
||||||
dateFormatter: DateFormatter = FakeDateFormatter(),
|
|
||||||
roomLatestEventFormatter: RoomLatestEventFormatter = FakeRoomLatestEventFormatter(),
|
|
||||||
): RoomListFiltersPresenter {
|
|
||||||
return RoomListFiltersPresenter(
|
return RoomListFiltersPresenter(
|
||||||
filterSelectionStrategy = DefaultFilterSelectionStrategy(),
|
filterSelectionStrategy = DefaultFilterSelectionStrategy(),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,223 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.home.impl.spacefilters
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||||
|
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||||
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
import io.element.android.libraries.matrix.test.FakeMatrixClient
|
||||||
|
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
|
||||||
|
import io.element.android.tests.testutils.awaitLastSequentialItem
|
||||||
|
import io.element.android.tests.testutils.test
|
||||||
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
|
import kotlinx.coroutines.test.runTest
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@OptIn(ExperimentalCoroutinesApi::class)
|
||||||
|
class SpaceFiltersPresenterTest {
|
||||||
|
@Test
|
||||||
|
fun `present - when feature flag is disabled returns Disabled state`() = runTest {
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to false)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitItem()
|
||||||
|
assertThat(state).isEqualTo(SpaceFiltersState.Disabled)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - when feature flag is enabled returns Unselected state initially`() = runTest {
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val state = awaitLastSequentialItem()
|
||||||
|
assertThat(state).isInstanceOf(SpaceFiltersState.Unselected::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - ShowFilters event transitions from Unselected to Selecting`() = runTest {
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
val selectingState = awaitLastSequentialItem()
|
||||||
|
assertThat(selectingState).isInstanceOf(SpaceFiltersState.Selecting::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - Cancel event in Selecting state transitions back to Unselected`() = runTest {
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Now in Selecting
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.Cancel)
|
||||||
|
|
||||||
|
// Back to Unselected
|
||||||
|
val finalState = awaitLastSequentialItem()
|
||||||
|
assertThat(finalState).isInstanceOf(SpaceFiltersState.Unselected::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - SelectFilter event in Selecting state transitions to Selected`() = runTest {
|
||||||
|
val spaceFilter = aSpaceServiceFilter(displayName = "Test Space")
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Now in Selecting
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(spaceFilter))
|
||||||
|
|
||||||
|
// Now in Selected
|
||||||
|
val selectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
assertThat(selectedState.selectedFilter).isEqualTo(spaceFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - ClearSelection event in Selected state transitions back to Unselected`() = runTest {
|
||||||
|
val spaceFilter = aSpaceServiceFilter(displayName = "Test Space")
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Now in Selecting
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(spaceFilter))
|
||||||
|
|
||||||
|
// Now in Selected
|
||||||
|
val selectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
selectedState.eventSink(SpaceFiltersEvent.Selected.ClearSelection)
|
||||||
|
|
||||||
|
// Back to Unselected
|
||||||
|
val finalState = awaitLastSequentialItem()
|
||||||
|
assertThat(finalState).isInstanceOf(SpaceFiltersState.Unselected::class.java)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - available filters are passed from SpaceService`() = runTest {
|
||||||
|
val spaceFilter1 = aSpaceServiceFilter(displayName = "Work", roomId = RoomId("!work:example.com"))
|
||||||
|
val spaceFilter2 = aSpaceServiceFilter(displayName = "Personal", roomId = RoomId("!personal:example.com"))
|
||||||
|
val spaceFilters = listOf(spaceFilter1, spaceFilter2)
|
||||||
|
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Emit space filters
|
||||||
|
spaceService.emitSpaceFilters(spaceFilters)
|
||||||
|
|
||||||
|
// Now in Selecting with available filters
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
assertThat(selectingState.availableFilters).containsExactly(spaceFilter1, spaceFilter2).inOrder()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `present - selected filter stays in sync when available filters update`() = runTest {
|
||||||
|
val originalFilter = aSpaceServiceFilter(
|
||||||
|
displayName = "Work",
|
||||||
|
roomId = RoomId("!work:example.com"),
|
||||||
|
descendants = listOf(RoomId("!room1:example.com"))
|
||||||
|
)
|
||||||
|
val updatedFilter = aSpaceServiceFilter(
|
||||||
|
displayName = "Work",
|
||||||
|
roomId = RoomId("!work:example.com"),
|
||||||
|
descendants = listOf(RoomId("!room1:example.com"), RoomId("!room2:example.com"))
|
||||||
|
)
|
||||||
|
|
||||||
|
val spaceService = FakeSpaceService()
|
||||||
|
val matrixClient = FakeMatrixClient(spaceService = spaceService)
|
||||||
|
|
||||||
|
val presenter = createSpaceFiltersPresenter(
|
||||||
|
featureFlagService = FakeFeatureFlagService(
|
||||||
|
initialState = mapOf(FeatureFlags.RoomListSpaceFilters.key to true)
|
||||||
|
),
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
presenter.test {
|
||||||
|
// Start in Unselected
|
||||||
|
val unselectedState = awaitLastSequentialItem() as SpaceFiltersState.Unselected
|
||||||
|
unselectedState.eventSink(SpaceFiltersEvent.Unselected.ShowFilters)
|
||||||
|
|
||||||
|
// Emit initial space filters
|
||||||
|
spaceService.emitSpaceFilters(listOf(originalFilter))
|
||||||
|
|
||||||
|
// Now in Selecting
|
||||||
|
val selectingState = awaitLastSequentialItem() as SpaceFiltersState.Selecting
|
||||||
|
selectingState.eventSink(SpaceFiltersEvent.Selecting.SelectFilter(originalFilter))
|
||||||
|
|
||||||
|
// Now in Selected
|
||||||
|
val selectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
assertThat(selectedState.selectedFilter.descendants).hasSize(1)
|
||||||
|
|
||||||
|
// Emit updated space filters
|
||||||
|
spaceService.emitSpaceFilters(listOf(updatedFilter))
|
||||||
|
|
||||||
|
// Selected filter should be updated
|
||||||
|
val updatedSelectedState = awaitLastSequentialItem() as SpaceFiltersState.Selected
|
||||||
|
assertThat(updatedSelectedState.selectedFilter.descendants).hasSize(2)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createSpaceFiltersPresenter(
|
||||||
|
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
|
||||||
|
matrixClient: FakeMatrixClient = FakeMatrixClient(),
|
||||||
|
): SpaceFiltersPresenter {
|
||||||
|
return SpaceFiltersPresenter(
|
||||||
|
featureFlagService = featureFlagService,
|
||||||
|
matrixClient = matrixClient,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,80 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.home.impl.spacefilters
|
||||||
|
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
|
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||||
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import org.junit.Rule
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.rules.TestRule
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class SpaceFiltersViewTest {
|
||||||
|
@get:Rule
|
||||||
|
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `clicking on a filter with alias shows display name and alias`() {
|
||||||
|
val filter = aSpaceServiceFilter(
|
||||||
|
displayName = "Test Space",
|
||||||
|
canonicalAlias = A_ROOM_ALIAS,
|
||||||
|
)
|
||||||
|
val eventsRecorder = EventsRecorder<SpaceFiltersEvent.Selecting>()
|
||||||
|
rule.setSpaceFiltersView(
|
||||||
|
state = aSelectingSpaceFiltersState(
|
||||||
|
availableFilters = listOf(filter),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Both display name and alias should be visible
|
||||||
|
rule.onNodeWithText(filter.spaceRoom.displayName).assertExists()
|
||||||
|
rule.onNodeWithText(A_ROOM_ALIAS.value).assertExists()
|
||||||
|
|
||||||
|
rule.onNodeWithText(filter.spaceRoom.displayName).performClick()
|
||||||
|
|
||||||
|
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter))
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `multiple filters are displayed and clickable`() {
|
||||||
|
val filter1 = aSpaceServiceFilter(displayName = "Space One")
|
||||||
|
val filter2 = aSpaceServiceFilter(displayName = "Space Two")
|
||||||
|
val eventsRecorder = EventsRecorder<SpaceFiltersEvent.Selecting>()
|
||||||
|
rule.setSpaceFiltersView(
|
||||||
|
state = aSelectingSpaceFiltersState(
|
||||||
|
availableFilters = listOf(filter1, filter2),
|
||||||
|
eventSink = eventsRecorder,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
// Both filters should be visible
|
||||||
|
rule.onNodeWithText(filter1.spaceRoom.displayName).assertExists()
|
||||||
|
rule.onNodeWithText(filter2.spaceRoom.displayName).assertExists()
|
||||||
|
|
||||||
|
// Click on second filter
|
||||||
|
rule.onNodeWithText(filter2.spaceRoom.displayName).performClick()
|
||||||
|
|
||||||
|
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter2))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceFiltersView(
|
||||||
|
state: SpaceFiltersState,
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
SpaceFiltersView(state = state)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -44,8 +44,8 @@ sealed interface RoomListFilter {
|
||||||
) : RoomListFilter
|
) : RoomListFilter
|
||||||
|
|
||||||
data class Identifiers(
|
data class Identifiers(
|
||||||
val values : List<RoomId>,
|
val values: List<RoomId>,
|
||||||
): RoomListFilter
|
) : RoomListFilter
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A filter that matches rooms that are unread.
|
* A filter that matches rooms that are unread.
|
||||||
|
|
|
||||||
|
|
@ -37,12 +37,12 @@ class FakeSpaceService(
|
||||||
_topLevelSpacesFlow.emit(value)
|
_topLevelSpacesFlow.emit(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
private val _spaceServiceFiltersFlow = MutableSharedFlow<List<SpaceServiceFilter>>()
|
private val _spaceFiltersFlow = MutableSharedFlow<List<SpaceServiceFilter>>()
|
||||||
override val spaceFiltersFlow: SharedFlow<List<SpaceServiceFilter>>
|
override val spaceFiltersFlow: SharedFlow<List<SpaceServiceFilter>>
|
||||||
get() = _spaceServiceFiltersFlow.asSharedFlow()
|
get() = _spaceFiltersFlow.asSharedFlow()
|
||||||
|
|
||||||
suspend fun emitSpaceFilters(value: List<SpaceServiceFilter>) {
|
suspend fun emitSpaceFilters(value: List<SpaceServiceFilter>) {
|
||||||
_spaceServiceFiltersFlow.emit(value)
|
_spaceFiltersFlow.emit(value)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>> {
|
override suspend fun joinedParents(spaceId: RoomId): Result<List<SpaceRoom>> {
|
||||||
|
|
|
||||||
|
|
@ -89,6 +89,7 @@ class KonsistPreviewTest {
|
||||||
"GradientFloatingActionButtonCircleShapePreview",
|
"GradientFloatingActionButtonCircleShapePreview",
|
||||||
"HeaderFooterPageScrollablePreview",
|
"HeaderFooterPageScrollablePreview",
|
||||||
"HomeTopBarMultiAccountPreview",
|
"HomeTopBarMultiAccountPreview",
|
||||||
|
"HomeTopBarSpaceFiltersSelectedPreview",
|
||||||
"HomeTopBarSpacesPreview",
|
"HomeTopBarSpacesPreview",
|
||||||
"HomeTopBarWithIndicatorPreview",
|
"HomeTopBarWithIndicatorPreview",
|
||||||
"IconsOtherPreview",
|
"IconsOtherPreview",
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue