Browse Source

Merge pull request #2173 from element-hq/feature/fga/invite_user_loader

Feature/fga/invite user loader
pull/2179/head
ganfra 9 months ago committed by GitHub
parent
commit
78f4f7a60f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 1
      changelog.d/2172.bugfix
  2. 7
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchUserBar.kt
  3. 1
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt
  4. 20
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt
  5. 1
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt
  6. 6
      features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt
  7. 36
      features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt
  8. 30
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt
  9. 1
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt
  10. 17
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt
  11. 8
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt
  12. 6
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt
  13. 4
      features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt
  14. 57
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt
  15. 4
      features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt
  16. 19
      libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt
  17. 4
      libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt
  18. 2
      libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt
  19. 6
      libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt
  20. 2
      libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt
  21. 5
      libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt
  22. 26
      libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt
  23. 45
      libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt
  24. 10
      libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt
  25. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_8,NEXUS_5,1.0,en].png
  26. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_8,NEXUS_5,1.0,en].png
  27. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Day-1_2_null_7,NEXUS_5,1.0,en].png
  28. BIN
      tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Night-1_3_null_7,NEXUS_5,1.0,en].png
  29. 0
      tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_SearchBarActiveNoneQuery_null_Searchviews_SearchBarActiveNoneQuery_0_null,NEXUS_5,1.0,en].png

1
changelog.d/2172.bugfix

@ -0,0 +1 @@ @@ -0,0 +1 @@
Fix no indication that user list is loading when inviting to room.

7
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/SearchUserBar.kt

@ -35,6 +35,7 @@ import androidx.compose.runtime.remember @@ -35,6 +35,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.unit.dp
import io.element.android.libraries.designsystem.components.async.AsyncLoading
import io.element.android.libraries.designsystem.theme.components.HorizontalDivider
import io.element.android.libraries.designsystem.theme.components.SearchBar
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
@ -49,6 +50,7 @@ import kotlinx.collections.immutable.ImmutableList @@ -49,6 +50,7 @@ import kotlinx.collections.immutable.ImmutableList
fun SearchUserBar(
query: String,
state: SearchBarResultState<ImmutableList<UserSearchResult>>,
showLoader: Boolean,
selectedUsers: ImmutableList<MatrixUser>,
active: Boolean,
isMultiSelectionEnabled: Boolean,
@ -99,6 +101,11 @@ fun SearchUserBar( @@ -99,6 +101,11 @@ fun SearchUserBar(
)
}
},
contentSuffix = {
if (showLoader) {
AsyncLoading()
}
},
resultState = state,
resultHandler = { users ->
LazyColumn(state = columnState) {

1
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/components/UserListView.kt

@ -48,6 +48,7 @@ fun UserListView( @@ -48,6 +48,7 @@ fun UserListView(
state = state.searchResults,
selectedUsers = state.selectedUsers,
active = state.isSearchActive,
showLoader = state.showSearchLoader,
isMultiSelectionEnabled = state.isMultiSelectionEnabled,
showBackButton = showBackButton,
onActiveChanged = { state.eventSink(UserListEvents.OnSearchActiveChanged(it)) },

20
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenter.kt

@ -34,6 +34,8 @@ import io.element.android.libraries.usersearch.api.UserRepository @@ -34,6 +34,8 @@ import io.element.android.libraries.usersearch.api.UserRepository
import io.element.android.libraries.usersearch.api.UserSearchResult
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
class DefaultUserListPresenter @AssistedInject constructor(
@Assisted val args: UserListPresenterArgs,
@ -57,18 +59,21 @@ class DefaultUserListPresenter @AssistedInject constructor( @@ -57,18 +59,21 @@ class DefaultUserListPresenter @AssistedInject constructor(
val selectedUsers by userListDataStore.selectedUsers().collectAsState(emptyList())
var searchQuery by rememberSaveable { mutableStateOf("") }
var searchResults: SearchBarResultState<ImmutableList<UserSearchResult>> by remember {
mutableStateOf(SearchBarResultState.NotSearching())
mutableStateOf(SearchBarResultState.Initial())
}
var showSearchLoader by remember { mutableStateOf(false) }
LaunchedEffect(searchQuery) {
searchResults = SearchBarResultState.NotSearching()
userRepository.search(searchQuery).collect {
searchResults = SearchBarResultState.Initial()
showSearchLoader = false
userRepository.search(searchQuery).onEach { state ->
showSearchLoader = state.isSearching
searchResults = when {
it.isEmpty() -> SearchBarResultState.NoResults()
else -> SearchBarResultState.Results(it.toImmutableList())
}
state.results.isEmpty() && state.isSearching -> SearchBarResultState.Initial()
state.results.isEmpty() && !state.isSearching -> SearchBarResultState.NoResultsFound()
else -> SearchBarResultState.Results(state.results.toImmutableList())
}
}.launchIn(this)
}
return UserListState(
@ -76,6 +81,7 @@ class DefaultUserListPresenter @AssistedInject constructor( @@ -76,6 +81,7 @@ class DefaultUserListPresenter @AssistedInject constructor(
searchResults = searchResults,
selectedUsers = selectedUsers.toImmutableList(),
isSearchActive = isSearchActive,
showSearchLoader = showSearchLoader,
selectionMode = args.selectionMode,
eventSink = { event ->
when (event) {

1
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListState.kt

@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList @@ -24,6 +24,7 @@ import kotlinx.collections.immutable.ImmutableList
data class UserListState(
val searchQuery: String,
val searchResults: SearchBarResultState<ImmutableList<UserSearchResult>>,
val showSearchLoader: Boolean,
val selectedUsers: ImmutableList<MatrixUser>,
val isSearchActive: Boolean,
val selectionMode: SelectionMode,

6
features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/userlist/UserListStateProvider.kt

@ -51,17 +51,19 @@ open class UserListStateProvider : PreviewParameterProvider<UserListState> { @@ -51,17 +51,19 @@ open class UserListStateProvider : PreviewParameterProvider<UserListState> {
aUserListState().copy(
isSearchActive = true,
searchQuery = "something-with-no-results",
searchResults = SearchBarResultState.NoResults()
searchResults = SearchBarResultState.NoResultsFound()
),
aUserListState().copy(isSearchActive = true, searchQuery = "someone", selectionMode = SelectionMode.Single),
)
}
fun aUserListState() = UserListState(
isSearchActive = false,
searchQuery = "",
searchResults = SearchBarResultState.NotSearching(),
searchResults = SearchBarResultState.Initial(),
selectedUsers = persistentListOf(),
selectionMode = SelectionMode.Single,
showSearchLoader = false,
eventSink = {}
)

36
features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/userlist/DefaultUserListPresenterTests.kt

@ -24,6 +24,7 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul @@ -24,6 +24,7 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.usersearch.api.UserSearchResult
import io.element.android.libraries.usersearch.api.UserSearchResultState
import io.element.android.libraries.usersearch.test.FakeUserRepository
import io.element.android.tests.testutils.WarmUpRule
import kotlinx.collections.immutable.persistentListOf
@ -55,7 +56,7 @@ class DefaultUserListPresenterTests { @@ -55,7 +56,7 @@ class DefaultUserListPresenterTests {
assertThat(initialState.isMultiSelectionEnabled).isFalse()
assertThat(initialState.isSearchActive).isFalse()
assertThat(initialState.selectedUsers).isEmpty()
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
}
}
@ -76,7 +77,7 @@ class DefaultUserListPresenterTests { @@ -76,7 +77,7 @@ class DefaultUserListPresenterTests {
assertThat(initialState.isMultiSelectionEnabled).isTrue()
assertThat(initialState.isSearchActive).isFalse()
assertThat(initialState.selectedUsers).isEmpty()
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
}
}
@ -131,25 +132,38 @@ class DefaultUserListPresenterTests { @@ -131,25 +132,38 @@ class DefaultUserListPresenterTests {
val initialState = awaitItem()
initialState.eventSink(UserListEvents.UpdateSearchQuery("alice"))
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(userRepository.providedQuery).isEqualTo("alice")
skipItems(2)
// When the user repository emits a result, it's copied to the state
userRepository.emitResult(listOf(UserSearchResult(aMatrixUser())))
assertThat(awaitItem().searchResults).isEqualTo(
val result = UserSearchResultState(
results = listOf(UserSearchResult(aMatrixUser())),
isSearching = false,
)
userRepository.emitState(result)
awaitItem().also { state ->
assertThat(state.searchResults).isEqualTo(
SearchBarResultState.Results(
persistentListOf(UserSearchResult(aMatrixUser()))
)
)
assertThat(state.showSearchLoader).isFalse()
}
// When the user repository emits another result, it replaces the previous value
userRepository.emitResult(aMatrixUserList().map { UserSearchResult(it) })
assertThat(awaitItem().searchResults).isEqualTo(
val newResult = UserSearchResultState(
results = aMatrixUserList().map { UserSearchResult(it) },
isSearching = false,
)
userRepository.emitState(newResult)
awaitItem().also { state ->
assertThat(state.searchResults).isEqualTo(
SearchBarResultState.Results(
aMatrixUserList().map { UserSearchResult(it) }
)
)
assertThat(state.showSearchLoader).isFalse()
}
}
}
@ -170,13 +184,13 @@ class DefaultUserListPresenterTests { @@ -170,13 +184,13 @@ class DefaultUserListPresenterTests {
val initialState = awaitItem()
initialState.eventSink(UserListEvents.UpdateSearchQuery("alice"))
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(userRepository.providedQuery).isEqualTo("alice")
skipItems(2)
// When the results list is empty, the state is set to NoResults
userRepository.emitResult(emptyList())
assertThat(awaitItem().searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java)
userRepository.emitState(UserSearchResultState(results = emptyList(), isSearching = false))
assertThat(awaitItem().searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
}
}

30
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenter.kt

@ -37,6 +37,8 @@ import io.element.android.libraries.usersearch.api.UserRepository @@ -37,6 +37,8 @@ import io.element.android.libraries.usersearch.api.UserRepository
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import javax.inject.Inject
@ -50,16 +52,22 @@ class RoomInviteMembersPresenter @Inject constructor( @@ -50,16 +52,22 @@ class RoomInviteMembersPresenter @Inject constructor(
override fun present(): RoomInviteMembersState {
val roomMembers = remember { mutableStateOf<AsyncData<ImmutableList<RoomMember>>>(AsyncData.Loading()) }
val selectedUsers = remember { mutableStateOf<ImmutableList<MatrixUser>>(persistentListOf()) }
val searchResults = remember { mutableStateOf<SearchBarResultState<ImmutableList<InvitableUser>>>(SearchBarResultState.NotSearching()) }
val searchResults = remember { mutableStateOf<SearchBarResultState<ImmutableList<InvitableUser>>>(SearchBarResultState.Initial()) }
var searchQuery by rememberSaveable { mutableStateOf("") }
var searchActive by rememberSaveable { mutableStateOf(false) }
var showSearchLoader = rememberSaveable { mutableStateOf(false) }
LaunchedEffect(Unit) {
fetchMembers(roomMembers)
}
LaunchedEffect(searchQuery, roomMembers) {
performSearch(searchResults, roomMembers, selectedUsers, searchQuery)
performSearch(
searchResults = searchResults,
roomMembers = roomMembers,
selectedUsers = selectedUsers,
showSearchLoader = showSearchLoader,
searchQuery = searchQuery
)
}
return RoomInviteMembersState(
@ -68,6 +76,7 @@ class RoomInviteMembersPresenter @Inject constructor( @@ -68,6 +76,7 @@ class RoomInviteMembersPresenter @Inject constructor(
searchQuery = searchQuery,
isSearchActive = searchActive,
searchResults = searchResults.value,
showSearchLoader = showSearchLoader.value,
eventSink = {
when (it) {
is RoomInviteMembersEvents.OnSearchActiveChanged -> {
@ -117,16 +126,19 @@ class RoomInviteMembersPresenter @Inject constructor( @@ -117,16 +126,19 @@ class RoomInviteMembersPresenter @Inject constructor(
searchResults: MutableState<SearchBarResultState<ImmutableList<InvitableUser>>>,
roomMembers: MutableState<AsyncData<ImmutableList<RoomMember>>>,
selectedUsers: MutableState<ImmutableList<MatrixUser>>,
showSearchLoader: MutableState<Boolean>,
searchQuery: String,
) = withContext(coroutineDispatchers.io) {
searchResults.value = SearchBarResultState.NotSearching()
searchResults.value = SearchBarResultState.Initial()
showSearchLoader.value = false
val joinedMembers = roomMembers.value.dataOrNull().orEmpty()
userRepository.search(searchQuery).collect {
userRepository.search(searchQuery).onEach { state ->
showSearchLoader.value = state.isSearching
searchResults.value = when {
it.isEmpty() -> SearchBarResultState.NoResults()
else -> SearchBarResultState.Results(it.map { result ->
state.results.isEmpty() && state.isSearching -> SearchBarResultState.Initial()
state.results.isEmpty() && !state.isSearching -> SearchBarResultState.NoResultsFound()
else -> SearchBarResultState.Results(state.results.map { result ->
val existingMembership = joinedMembers.firstOrNull { j -> j.userId == result.matrixUser.userId }?.membership
val isJoined = existingMembership == RoomMembershipState.JOIN
val isInvited = existingMembership == RoomMembershipState.INVITE
@ -139,7 +151,7 @@ class RoomInviteMembersPresenter @Inject constructor( @@ -139,7 +151,7 @@ class RoomInviteMembersPresenter @Inject constructor(
)
}.toImmutableList())
}
}
}.launchIn(this)
}
private suspend fun fetchMembers(roomMembers: MutableState<AsyncData<ImmutableList<RoomMember>>>) {

1
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersState.kt

@ -23,6 +23,7 @@ import kotlinx.collections.immutable.ImmutableList @@ -23,6 +23,7 @@ import kotlinx.collections.immutable.ImmutableList
data class RoomInviteMembersState(
val canInvite: Boolean,
val searchQuery: String,
val showSearchLoader: Boolean,
val searchResults: SearchBarResultState<ImmutableList<InvitableUser>>,
val selectedUsers: ImmutableList<MatrixUser>,
val isSearchActive: Boolean,

17
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersStateProvider.kt

@ -32,7 +32,7 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInv @@ -32,7 +32,7 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInv
aRoomInviteMembersState(canInvite = true, selectedUsers = aMatrixUserList().toImmutableList()),
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query"),
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", selectedUsers = aMatrixUserList().toImmutableList()),
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", searchResults = SearchBarResultState.NoResults()),
aRoomInviteMembersState(isSearchActive = true, searchQuery = "some query", searchResults = SearchBarResultState.NoResultsFound()),
aRoomInviteMembersState(
isSearchActive = true,
canInvite = true,
@ -64,15 +64,27 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInv @@ -64,15 +64,27 @@ internal class RoomInviteMembersStateProvider : PreviewParameterProvider<RoomInv
)
)
),
aRoomInviteMembersState(
isSearchActive = true,
canInvite = true,
searchQuery = "@alice:server.org",
searchResults = SearchBarResultState.Results(
persistentListOf(
InvitableUser(aMatrixUser("@alice:server.org"), isUnresolved = true),
)
),
showSearchLoader = true,
),
)
}
private fun aRoomInviteMembersState(
canInvite: Boolean = false,
searchQuery: String = "",
searchResults: SearchBarResultState<ImmutableList<InvitableUser>> = SearchBarResultState.NotSearching(),
searchResults: SearchBarResultState<ImmutableList<InvitableUser>> = SearchBarResultState.Initial(),
selectedUsers: ImmutableList<MatrixUser> = persistentListOf(),
isSearchActive: Boolean = false,
showSearchLoader: Boolean = false,
): RoomInviteMembersState {
return RoomInviteMembersState(
canInvite = canInvite,
@ -80,6 +92,7 @@ private fun aRoomInviteMembersState( @@ -80,6 +92,7 @@ private fun aRoomInviteMembersState(
searchResults = searchResults,
selectedUsers = selectedUsers,
isSearchActive = isSearchActive,
showSearchLoader = showSearchLoader,
eventSink = {},
)
}

8
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersView.kt

@ -50,6 +50,7 @@ import io.element.android.libraries.matrix.ui.components.SelectedUsersList @@ -50,6 +50,7 @@ import io.element.android.libraries.matrix.ui.components.SelectedUsersList
import io.element.android.libraries.matrix.ui.model.getAvatarData
import io.element.android.libraries.matrix.ui.model.getBestName
import io.element.android.compound.theme.ElementTheme
import io.element.android.libraries.designsystem.components.async.AsyncLoading
import io.element.android.libraries.ui.strings.CommonStrings
import kotlinx.collections.immutable.ImmutableList
@ -86,6 +87,7 @@ fun RoomInviteMembersView( @@ -86,6 +87,7 @@ fun RoomInviteMembersView(
RoomInviteMembersSearchBar(
modifier = Modifier.fillMaxWidth(),
query = state.searchQuery,
showLoader = state.showSearchLoader,
selectedUsers = state.selectedUsers,
state = state.searchResults,
active = state.isSearchActive,
@ -139,6 +141,7 @@ private fun RoomInviteMembersTopBar( @@ -139,6 +141,7 @@ private fun RoomInviteMembersTopBar(
private fun RoomInviteMembersSearchBar(
query: String,
state: SearchBarResultState<ImmutableList<InvitableUser>>,
showLoader: Boolean,
selectedUsers: ImmutableList<MatrixUser>,
active: Boolean,
onActiveChanged: (Boolean) -> Unit,
@ -167,6 +170,11 @@ private fun RoomInviteMembersSearchBar( @@ -167,6 +170,11 @@ private fun RoomInviteMembersSearchBar(
},
showBackButton = false,
resultState = state,
contentSuffix = {
if (showLoader) {
AsyncLoading()
}
},
resultHandler = { results ->
Text(
text = stringResource(id = CommonStrings.common_search_results),

6
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListPresenter.kt

@ -47,7 +47,7 @@ class RoomMemberListPresenter @Inject constructor( @@ -47,7 +47,7 @@ class RoomMemberListPresenter @Inject constructor(
var roomMembers by remember { mutableStateOf<AsyncData<RoomMembers>>(AsyncData.Loading()) }
var searchQuery by rememberSaveable { mutableStateOf("") }
var searchResults by remember {
mutableStateOf<SearchBarResultState<RoomMembers>>(SearchBarResultState.NotSearching())
mutableStateOf<SearchBarResultState<RoomMembers>>(SearchBarResultState.Initial())
}
var isSearchActive by rememberSaveable { mutableStateOf(false) }
@ -71,10 +71,10 @@ class RoomMemberListPresenter @Inject constructor( @@ -71,10 +71,10 @@ class RoomMemberListPresenter @Inject constructor(
LaunchedEffect(searchQuery) {
withContext(coroutineDispatchers.io) {
searchResults = if (searchQuery.isEmpty()) {
SearchBarResultState.NotSearching()
SearchBarResultState.Initial()
} else {
val results = roomMemberListDataSource.search(searchQuery).groupBy { it.membership }
if (results.isEmpty()) SearchBarResultState.NoResults()
if (results.isEmpty()) SearchBarResultState.NoResultsFound()
else SearchBarResultState.Results(
RoomMembers(
invited = results.getOrDefault(RoomMembershipState.INVITE, emptyList()).toImmutableList(),

4
features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/RoomMemberListStateProvider.kt

@ -53,14 +53,14 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember @@ -53,14 +53,14 @@ internal class RoomMemberListStateProvider : PreviewParameterProvider<RoomMember
aRoomMemberListState().copy(
isSearchActive = true,
searchQuery = "something-with-no-results",
searchResults = SearchBarResultState.NoResults()
searchResults = SearchBarResultState.NoResultsFound()
),
)
}
internal fun aRoomMemberListState(
roomMembers: AsyncData<RoomMembers> = AsyncData.Uninitialized,
searchResults: SearchBarResultState<RoomMembers> = SearchBarResultState.NotSearching(),
searchResults: SearchBarResultState<RoomMembers> = SearchBarResultState.Initial(),
) = RoomMemberListState(
roomMembers = roomMembers,
searchQuery = "",

57
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/impl/invite/RoomInviteMembersPresenterTest.kt

@ -29,14 +29,17 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul @@ -29,14 +29,17 @@ import io.element.android.libraries.designsystem.theme.components.SearchBarResul
import io.element.android.libraries.matrix.api.room.MatrixRoom
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
import io.element.android.libraries.matrix.ui.components.aMatrixUser
import io.element.android.libraries.matrix.ui.components.aMatrixUserList
import io.element.android.libraries.usersearch.api.UserSearchResult
import io.element.android.libraries.usersearch.api.UserSearchResultState
import io.element.android.libraries.usersearch.test.FakeUserRepository
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.testCoroutineDispatchers
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
@ -63,7 +66,7 @@ internal class RoomInviteMembersPresenterTest { @@ -63,7 +66,7 @@ internal class RoomInviteMembersPresenterTest {
}.test {
val initialState = awaitItem()
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(initialState.isSearchActive).isFalse()
assertThat(initialState.canInvite).isFalse()
assertThat(initialState.searchQuery).isEmpty()
@ -94,7 +97,7 @@ internal class RoomInviteMembersPresenterTest { @@ -94,7 +97,7 @@ internal class RoomInviteMembersPresenterTest {
}
@Test
fun `present - performs search and handles no results`() = runTest {
fun `present - performs search and handles empty result list`() = runTest {
val repository = FakeUserRepository()
val presenter = RoomInviteMembersPresenter(
userRepository = repository,
@ -105,17 +108,18 @@ internal class RoomInviteMembersPresenterTest { @@ -105,17 +108,18 @@ internal class RoomInviteMembersPresenterTest {
presenter.present()
}.test {
val initialState = awaitItem()
skipItems(1)
initialState.eventSink(RoomInviteMembersEvents.UpdateSearchQuery("some query"))
skipItems(1)
assertThat(repository.providedQuery).isEqualTo("some query")
repository.emitResult(emptyList())
skipItems(1)
val resultState = awaitItem()
assertThat(resultState.searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java)
repository.emitState(UserSearchResultState(results = emptyList(), isSearching = true))
consumeItemsUntilPredicate { it.showSearchLoader }.last().also { state ->
assertThat(state.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(state.showSearchLoader).isTrue()
}
repository.emitState(results = emptyList(), isSearching = false)
consumeItemsUntilPredicate { !it.showSearchLoader }.last().also { state ->
assertThat(state.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
assertThat(state.showSearchLoader).isFalse()
}
}
}
@ -137,7 +141,7 @@ internal class RoomInviteMembersPresenterTest { @@ -137,7 +141,7 @@ internal class RoomInviteMembersPresenterTest {
skipItems(1)
assertThat(repository.providedQuery).isEqualTo("some query")
repository.emitResult(aMatrixUserList().map { UserSearchResult(it) })
repository.emitStateWithUsers(users = aMatrixUserList())
skipItems(1)
val resultState = awaitItem()
@ -189,7 +193,7 @@ internal class RoomInviteMembersPresenterTest { @@ -189,7 +193,7 @@ internal class RoomInviteMembersPresenterTest {
skipItems(1)
assertThat(repository.providedQuery).isEqualTo("some query")
repository.emitResult(aMatrixUserList().map { UserSearchResult(it) })
repository.emitStateWithUsers(users = aMatrixUserList())
skipItems(1)
val resultState = awaitItem()
@ -250,7 +254,7 @@ internal class RoomInviteMembersPresenterTest { @@ -250,7 +254,7 @@ internal class RoomInviteMembersPresenterTest {
assertThat(repository.providedQuery).isEqualTo("some query")
val unresolvedUser = UserSearchResult(aMatrixUser(id = A_USER_ID.value), isUnresolved = true)
repository.emitResult(listOf(unresolvedUser) + aMatrixUserList().map { UserSearchResult(it) })
repository.emitState(listOf(unresolvedUser) + aMatrixUserList().map { UserSearchResult(it) })
skipItems(1)
val resultState = awaitItem()
@ -318,7 +322,7 @@ internal class RoomInviteMembersPresenterTest { @@ -318,7 +322,7 @@ internal class RoomInviteMembersPresenterTest {
skipItems(1)
assertThat(repository.providedQuery).isEqualTo("some query")
repository.emitResult((aMatrixUserList() + selectedUser).map { UserSearchResult(it) })
repository.emitStateWithUsers(users = aMatrixUserList() + selectedUser)
skipItems(2)
val resultState = awaitItem()
@ -358,7 +362,7 @@ internal class RoomInviteMembersPresenterTest { @@ -358,7 +362,7 @@ internal class RoomInviteMembersPresenterTest {
skipItems(1)
assertThat(repository.providedQuery).isEqualTo("some query")
repository.emitResult((aMatrixUserList() + selectedUser).map { UserSearchResult(it) })
repository.emitStateWithUsers(users = aMatrixUserList() + selectedUser)
skipItems(2)
// And then a user is toggled
@ -381,6 +385,27 @@ internal class RoomInviteMembersPresenterTest { @@ -381,6 +385,27 @@ internal class RoomInviteMembersPresenterTest {
}
}
private suspend fun FakeUserRepository.emitStateWithUsers(
users: List<MatrixUser>,
isSearching: Boolean = false
) {
emitState(
results = users.map { UserSearchResult(it) },
isSearching = isSearching,
)
}
private suspend fun FakeUserRepository.emitState(
results: List<UserSearchResult>,
isSearching: Boolean = false
) {
val state = UserSearchResultState(
results = results,
isSearching = isSearching
)
emitState(state)
}
private fun TestScope.createDataSource(
matrixRoom: MatrixRoom = aMatrixRoom().apply {
givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList()))

4
features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTests.kt

@ -55,7 +55,7 @@ class RoomMemberListPresenterTests { @@ -55,7 +55,7 @@ class RoomMemberListPresenterTests {
val initialState = awaitItem()
assertThat(initialState.roomMembers).isInstanceOf(AsyncData.Loading::class.java)
assertThat(initialState.searchQuery).isEmpty()
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.searchResults).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(initialState.isSearchActive).isFalse()
val loadedState = awaitItem()
assertThat(loadedState.roomMembers).isInstanceOf(AsyncData.Success::class.java)
@ -92,7 +92,7 @@ class RoomMemberListPresenterTests { @@ -92,7 +92,7 @@ class RoomMemberListPresenterTests {
val searchQueryUpdatedState = awaitItem()
assertThat(searchQueryUpdatedState.searchQuery).isEqualTo("something")
val searchSearchResultDelivered = awaitItem()
assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResults::class.java)
assertThat(searchSearchResultDelivered.searchResults).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
}
}

19
libraries/designsystem/src/main/kotlin/io/element/android/libraries/designsystem/theme/components/SearchBar.kt

@ -59,7 +59,7 @@ fun <T> SearchBar( @@ -59,7 +59,7 @@ fun <T> SearchBar(
modifier: Modifier = Modifier,
enabled: Boolean = true,
showBackButton: Boolean = true,
resultState: SearchBarResultState<T> = SearchBarResultState.NotSearching(),
resultState: SearchBarResultState<T> = SearchBarResultState.Initial(),
shape: Shape = SearchBarDefaults.inputFieldShape,
tonalElevation: Dp = SearchBarDefaults.TonalElevation,
windowInsets: WindowInsets = SearchBarDefaults.windowInsets,
@ -129,7 +129,7 @@ fun <T> SearchBar( @@ -129,7 +129,7 @@ fun <T> SearchBar(
resultHandler(resultState.results)
}
is SearchBarResultState.NoResults<T> -> {
is SearchBarResultState.NoResultsFound<T> -> {
// No results found, show a message
Spacer(Modifier.size(80.dp))
@ -184,10 +184,10 @@ object ElementSearchBarDefaults { @@ -184,10 +184,10 @@ object ElementSearchBarDefaults {
@Immutable
sealed interface SearchBarResultState<in T> {
/** No search results are available yet (e.g. because the user hasn't entered a search term). */
class NotSearching<T> : SearchBarResultState<T>
class Initial<T> : SearchBarResultState<T>
/** The search has completed, but no results were found. */
class NoResults<T> : SearchBarResultState<T>
class NoResultsFound<T> : SearchBarResultState<T>
/** The search has completed, and some matching users were found. */
data class Results<T>(val results: T) : SearchBarResultState<T>
@ -199,7 +199,7 @@ internal fun SearchBarInactivePreview() = ElementThemedPreview { ContentToPrevie @@ -199,7 +199,7 @@ internal fun SearchBarInactivePreview() = ElementThemedPreview { ContentToPrevie
@Preview(group = PreviewGroup.Search)
@Composable
internal fun SearchBarActiveEmptyQueryPreview() = ElementThemedPreview {
internal fun SearchBarActiveNoneQueryPreview() = ElementThemedPreview {
ContentToPreview(
query = "",
active = true,
@ -231,7 +231,7 @@ internal fun SearchBarActiveWithNoResultsPreview() = ElementThemedPreview { @@ -231,7 +231,7 @@ internal fun SearchBarActiveWithNoResultsPreview() = ElementThemedPreview {
ContentToPreview(
query = "search term",
active = true,
resultState = SearchBarResultState.NoResults(),
resultState = SearchBarResultState.NoResultsFound<String>(),
)
}
@ -257,8 +257,8 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview { @@ -257,8 +257,8 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview {
.background(color = Color.Blue)
.fillMaxWidth()
)
},
resultHandler = {
}
) {
Text(
text = "Results go here",
modifier = Modifier
@ -266,7 +266,6 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview { @@ -266,7 +266,6 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview {
.fillMaxWidth()
)
}
)
}
@OptIn(ExperimentalMaterial3Api::class)
@ -275,7 +274,7 @@ private fun ContentToPreview( @@ -275,7 +274,7 @@ private fun ContentToPreview(
query: String = "",
active: Boolean = false,
showBackButton: Boolean = true,
resultState: SearchBarResultState<String> = SearchBarResultState.NotSearching(),
resultState: SearchBarResultState<String> = SearchBarResultState.Initial(),
contentPrefix: @Composable ColumnScope.() -> Unit = {},
contentSuffix: @Composable ColumnScope.() -> Unit = {},
resultHandler: @Composable ColumnScope.(String) -> Unit = {},

4
libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenter.kt

@ -51,7 +51,7 @@ class RoomSelectPresenter @AssistedInject constructor( @@ -51,7 +51,7 @@ class RoomSelectPresenter @AssistedInject constructor(
var selectedRooms by remember { mutableStateOf(persistentListOf<RoomSummaryDetails>()) }
var query by remember { mutableStateOf("") }
var isSearchActive by remember { mutableStateOf(false) }
var results: SearchBarResultState<ImmutableList<RoomSummaryDetails>> by remember { mutableStateOf(SearchBarResultState.NotSearching()) }
var results: SearchBarResultState<ImmutableList<RoomSummaryDetails>> by remember { mutableStateOf(SearchBarResultState.Initial()) }
val summaries by client.roomListService.allRooms.summaries.collectAsState()
@ -64,7 +64,7 @@ class RoomSelectPresenter @AssistedInject constructor( @@ -64,7 +64,7 @@ class RoomSelectPresenter @AssistedInject constructor(
results = if (filteredSummaries.isNotEmpty()) {
SearchBarResultState.Results(filteredSummaries)
} else {
SearchBarResultState.NoResults()
SearchBarResultState.NoResultsFound()
}
}

2
libraries/roomselect/impl/src/main/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectStateProvider.kt

@ -48,7 +48,7 @@ open class RoomSelectStateProvider : PreviewParameterProvider<RoomSelectState> { @@ -48,7 +48,7 @@ open class RoomSelectStateProvider : PreviewParameterProvider<RoomSelectState> {
}
private fun aRoomSelectState(
resultState: SearchBarResultState<ImmutableList<RoomSummaryDetails>> = SearchBarResultState.NotSearching(),
resultState: SearchBarResultState<ImmutableList<RoomSummaryDetails>> = SearchBarResultState.Initial(),
query: String = "",
isSearchActive: Boolean = false,
selectedRooms: ImmutableList<RoomSummaryDetails> = persistentListOf(),

6
libraries/roomselect/impl/src/test/kotlin/io/element/android/libraries/roomselect/impl/RoomSelectPresenterTests.kt

@ -45,11 +45,11 @@ class RoomSelectPresenterTests { @@ -45,11 +45,11 @@ class RoomSelectPresenterTests {
}.test {
val initialState = awaitItem()
assertThat(initialState.selectedRooms).isEmpty()
assertThat(initialState.resultState).isInstanceOf(SearchBarResultState.NotSearching::class.java)
assertThat(initialState.resultState).isInstanceOf(SearchBarResultState.Initial::class.java)
assertThat(initialState.isSearchActive).isFalse()
// Search is run automatically
val searchState = awaitItem()
assertThat(searchState.resultState).isInstanceOf(SearchBarResultState.NoResults::class.java)
assertThat(searchState.resultState).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
}
}
@ -85,7 +85,7 @@ class RoomSelectPresenterTests { @@ -85,7 +85,7 @@ class RoomSelectPresenterTests {
initialState.eventSink(RoomSelectEvents.UpdateQuery("string not contained"))
assertThat(awaitItem().query).isEqualTo("string not contained")
assertThat(awaitItem().resultState).isInstanceOf(SearchBarResultState.NoResults::class.java)
assertThat(awaitItem().resultState).isInstanceOf(SearchBarResultState.NoResultsFound::class.java)
}
}

2
libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserRepository.kt

@ -20,5 +20,5 @@ import kotlinx.coroutines.flow.Flow @@ -20,5 +20,5 @@ import kotlinx.coroutines.flow.Flow
interface UserRepository {
suspend fun search(query: String): Flow<List<UserSearchResult>>
fun search(query: String): Flow<UserSearchResultState>
}

5
libraries/usersearch/api/src/main/kotlin/io/element/android/libraries/usersearch/api/UserSearchResult.kt

@ -22,3 +22,8 @@ data class UserSearchResult( @@ -22,3 +22,8 @@ data class UserSearchResult(
val matrixUser: MatrixUser,
val isUnresolved: Boolean = false,
)
data class UserSearchResultState(
val results: List<UserSearchResult>,
val isSearching: Boolean,
)

26
libraries/usersearch/impl/src/main/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepository.kt

@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser @@ -25,6 +25,7 @@ import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.usersearch.api.UserListDataSource
import io.element.android.libraries.usersearch.api.UserRepository
import io.element.android.libraries.usersearch.api.UserSearchResult
import io.element.android.libraries.usersearch.api.UserSearchResultState
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.flow
@ -36,17 +37,27 @@ class MatrixUserRepository @Inject constructor( @@ -36,17 +37,27 @@ class MatrixUserRepository @Inject constructor(
private val dataSource: UserListDataSource
) : UserRepository {
override suspend fun search(query: String): Flow<List<UserSearchResult>> = flow {
// If the search term is a MXID that's not ours, we'll show a 'fake' result for that user, then update it when we get search results.
override fun search(query: String): Flow<UserSearchResultState> = flow {
val shouldQueryProfile = MatrixPatterns.isUserId(query) && !client.isMe(UserId(query))
if (shouldQueryProfile) {
emit(listOf(UserSearchResult(MatrixUser(UserId(query)))))
val shouldFetchSearchResults = query.length >= MINIMUM_SEARCH_LENGTH
// If the search term is a MXID that's not ours, we'll show a 'fake' result for that user, then update it when we get search results.
val fakeSearchResult = if (shouldQueryProfile) {
UserSearchResult(MatrixUser(UserId(query)))
} else {
null
}
if (shouldQueryProfile || shouldFetchSearchResults) {
emit(UserSearchResultState(isSearching = shouldFetchSearchResults, results = listOfNotNull(fakeSearchResult)))
}
if (shouldFetchSearchResults) {
val results = fetchSearchResults(query, shouldQueryProfile)
emit(results)
}
}
if (query.length >= MINIMUM_SEARCH_LENGTH) {
private suspend fun fetchSearchResults(query: String, shouldQueryProfile: Boolean): UserSearchResultState {
// Debounce
delay(DEBOUNCE_TIME_MILLIS)
val results = dataSource
.search(query, MAXIMUM_SEARCH_RESULTS)
.filter { !client.isMe(it.userId) }
@ -62,8 +73,7 @@ class MatrixUserRepository @Inject constructor( @@ -62,8 +73,7 @@ class MatrixUserRepository @Inject constructor(
?: UserSearchResult(MatrixUser(UserId(query)), isUnresolved = true))
}
emit(results)
}
return UserSearchResultState(results = results, isSearching = false)
}
companion object {

45
libraries/usersearch/impl/src/test/kotlin/io/element/android/libraries/usersearch/impl/MatrixUserRepositoryTest.kt

@ -54,7 +54,14 @@ internal class MatrixUserRepositoryTest { @@ -54,7 +54,14 @@ internal class MatrixUserRepositoryTest {
val result = repository.search("some query")
result.test {
assertThat(awaitItem()).isEmpty()
awaitItem().also {
assertThat(it.isSearching).isTrue()
assertThat(it.results).isEmpty()
}
awaitItem().also {
assertThat(it.isSearching).isFalse()
assertThat(it.results).isEmpty()
}
awaitComplete()
}
}
@ -68,7 +75,14 @@ internal class MatrixUserRepositoryTest { @@ -68,7 +75,14 @@ internal class MatrixUserRepositoryTest {
val result = repository.search("some query")
result.test {
assertThat(awaitItem()).isEqualTo(aMatrixUserList().toUserSearchResults())
awaitItem().also {
assertThat(it.isSearching).isTrue()
assertThat(it.results).isEmpty()
}
awaitItem().also {
assertThat(it.isSearching).isFalse()
assertThat(it.results).isEqualTo(aMatrixUserList().toUserSearchResults())
}
awaitComplete()
}
}
@ -81,9 +95,11 @@ internal class MatrixUserRepositoryTest { @@ -81,9 +95,11 @@ internal class MatrixUserRepositoryTest {
val result = repository.search(A_USER_ID.value)
result.test {
assertThat(awaitItem()).isEqualTo(listOf(placeholderResult()))
skipItems(1)
awaitComplete()
awaitItem().also {
assertThat(it.isSearching).isTrue()
assertThat(it.results).isEqualTo(listOf(placeholderResult()))
}
cancelAndConsumeRemainingEvents()
}
}
@ -95,8 +111,11 @@ internal class MatrixUserRepositoryTest { @@ -95,8 +111,11 @@ internal class MatrixUserRepositoryTest {
val result = repository.search(SESSION_ID.value)
result.test {
assertThat(awaitItem()).isEmpty()
awaitComplete()
awaitItem().also {
assertThat(it.isSearching).isTrue()
assertThat(it.results).isEmpty()
}
cancelAndConsumeRemainingEvents()
}
}
@ -110,7 +129,8 @@ internal class MatrixUserRepositoryTest { @@ -110,7 +129,8 @@ internal class MatrixUserRepositoryTest {
val result = repository.search("some text")
result.test {
assertThat(awaitItem()).isEqualTo(aMatrixUserList().toUserSearchResults())
skipItems(1)
assertThat(awaitItem().results).isEqualTo(aMatrixUserList().toUserSearchResults())
awaitComplete()
}
}
@ -126,7 +146,7 @@ internal class MatrixUserRepositoryTest { @@ -126,7 +146,7 @@ internal class MatrixUserRepositoryTest {
result.test {
skipItems(1)
assertThat(awaitItem()).isEqualTo(searchResults.toUserSearchResults())
assertThat(awaitItem().results).isEqualTo(searchResults.toUserSearchResults())
awaitComplete()
}
}
@ -145,7 +165,7 @@ internal class MatrixUserRepositoryTest { @@ -145,7 +165,7 @@ internal class MatrixUserRepositoryTest {
result.test {
skipItems(1)
assertThat(awaitItem()).isEqualTo((listOf(userProfile) + searchResults).toUserSearchResults())
assertThat(awaitItem().results).isEqualTo((listOf(userProfile) + searchResults).toUserSearchResults())
awaitComplete()
}
}
@ -163,7 +183,8 @@ internal class MatrixUserRepositoryTest { @@ -163,7 +183,8 @@ internal class MatrixUserRepositoryTest {
val result = repository.search(SESSION_ID.value)
result.test {
assertThat(awaitItem()).isEqualTo(searchResults.toUserSearchResults())
skipItems(1)
assertThat(awaitItem().results).isEqualTo(searchResults.toUserSearchResults())
awaitComplete()
}
}
@ -181,7 +202,7 @@ internal class MatrixUserRepositoryTest { @@ -181,7 +202,7 @@ internal class MatrixUserRepositoryTest {
result.test {
skipItems(1)
assertThat(awaitItem()).isEqualTo(listOf(placeholderResult(isUnresolved = true)) + searchResults.toUserSearchResults())
assertThat(awaitItem().results).isEqualTo(listOf(placeholderResult(isUnresolved = true)) + searchResults.toUserSearchResults())
awaitComplete()
}
}

10
libraries/usersearch/test/src/main/kotlin/io/element/android/libraries/usersearch/test/FakeUserRepository.kt

@ -17,7 +17,7 @@ @@ -17,7 +17,7 @@
package io.element.android.libraries.usersearch.test
import io.element.android.libraries.usersearch.api.UserRepository
import io.element.android.libraries.usersearch.api.UserSearchResult
import io.element.android.libraries.usersearch.api.UserSearchResultState
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
@ -26,15 +26,15 @@ class FakeUserRepository : UserRepository { @@ -26,15 +26,15 @@ class FakeUserRepository : UserRepository {
var providedQuery: String? = null
private set
private val flow = MutableSharedFlow<List<UserSearchResult>>()
private val flow = MutableSharedFlow<UserSearchResultState>()
override suspend fun search(query: String): Flow<List<UserSearchResult>> {
override fun search(query: String): Flow<UserSearchResultState> {
providedQuery = query
return flow
}
suspend fun emitResult(result: List<UserSearchResult>) {
flow.emit(result)
suspend fun emitState(state: UserSearchResultState) {
flow.emit(state)
}
}

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Day-2_3_null_8,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.createroom.impl.components_UserListView_null_UserListView-Night-2_4_null_8,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Day-1_2_null_7,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

BIN
tests/uitests/src/test/snapshots/images/ui_S_t[f.roomdetails.impl.invite_RoomInviteMembers_null_RoomInviteMembers-Night-1_3_null_7,NEXUS_5,1.0,en].png (Stored with Git LFS)

Binary file not shown.

0
tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_SearchBarActiveEmptyQuery_null_Searchviews_SearchBarActiveEmptyQuery_0_null,NEXUS_5,1.0,en].png → tests/uitests/src/test/snapshots/images/ui_S_t[l.designsystem.theme.components_SearchBarActiveNoneQuery_null_Searchviews_SearchBarActiveNoneQuery_0_null,NEXUS_5,1.0,en].png

Loading…
Cancel
Save