Merge pull request #2173 from element-hq/feature/fga/invite_user_loader
Feature/fga/invite user loader
This commit is contained in:
commit
78f4f7a60f
29 changed files with 262 additions and 124 deletions
1
changelog.d/2172.bugfix
Normal file
1
changelog.d/2172.bugfix
Normal file
|
|
@ -0,0 +1 @@
|
|||
Fix no indication that user list is loading when inviting to room.
|
||||
|
|
@ -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
|
|||
fun SearchUserBar(
|
||||
query: String,
|
||||
state: SearchBarResultState<ImmutableList<UserSearchResult>>,
|
||||
showLoader: Boolean,
|
||||
selectedUsers: ImmutableList<MatrixUser>,
|
||||
active: Boolean,
|
||||
isMultiSelectionEnabled: Boolean,
|
||||
|
|
@ -99,6 +101,11 @@ fun SearchUserBar(
|
|||
)
|
||||
}
|
||||
},
|
||||
contentSuffix = {
|
||||
if (showLoader) {
|
||||
AsyncLoading()
|
||||
}
|
||||
},
|
||||
resultState = state,
|
||||
resultHandler = { users ->
|
||||
LazyColumn(state = columnState) {
|
||||
|
|
|
|||
|
|
@ -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)) },
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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(
|
|||
searchResults = searchResults,
|
||||
selectedUsers = selectedUsers.toImmutableList(),
|
||||
isSearchActive = isSearchActive,
|
||||
showSearchLoader = showSearchLoader,
|
||||
selectionMode = args.selectionMode,
|
||||
eventSink = { event ->
|
||||
when (event) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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 = {}
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
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 {
|
|||
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 {
|
|||
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(
|
||||
SearchBarResultState.Results(
|
||||
persistentListOf(UserSearchResult(aMatrixUser()))
|
||||
)
|
||||
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(
|
||||
SearchBarResultState.Results(
|
||||
aMatrixUserList().map { UserSearchResult(it) }
|
||||
)
|
||||
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 {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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(
|
|||
searchQuery = searchQuery,
|
||||
isSearchActive = searchActive,
|
||||
searchResults = searchResults.value,
|
||||
showSearchLoader = showSearchLoader.value,
|
||||
eventSink = {
|
||||
when (it) {
|
||||
is RoomInviteMembersEvents.OnSearchActiveChanged -> {
|
||||
|
|
@ -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(
|
|||
)
|
||||
}.toImmutableList())
|
||||
}
|
||||
}
|
||||
}.launchIn(this)
|
||||
}
|
||||
|
||||
private suspend fun fetchMembers(roomMembers: MutableState<AsyncData<ImmutableList<RoomMember>>>) {
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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
|
|||
)
|
||||
)
|
||||
),
|
||||
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(
|
|||
searchResults = searchResults,
|
||||
selectedUsers = selectedUsers,
|
||||
isSearchActive = isSearchActive,
|
||||
showSearchLoader = showSearchLoader,
|
||||
eventSink = {},
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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(
|
|||
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(
|
|||
},
|
||||
showBackButton = false,
|
||||
resultState = state,
|
||||
contentSuffix = {
|
||||
if (showLoader) {
|
||||
AsyncLoading()
|
||||
}
|
||||
},
|
||||
resultHandler = { results ->
|
||||
Text(
|
||||
text = stringResource(id = CommonStrings.common_search_results),
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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(),
|
||||
|
|
|
|||
|
|
@ -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 = "",
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
}.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 {
|
|||
}
|
||||
|
||||
@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 {
|
|||
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 {
|
|||
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 {
|
|||
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 {
|
|||
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 {
|
|||
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 {
|
|||
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 {
|
|||
}
|
||||
}
|
||||
|
||||
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()))
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
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 {
|
|||
@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
|
|||
|
||||
@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 {
|
|||
ContentToPreview(
|
||||
query = "search term",
|
||||
active = true,
|
||||
resultState = SearchBarResultState.NoResults(),
|
||||
resultState = SearchBarResultState.NoResultsFound<String>(),
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -257,16 +257,15 @@ internal fun SearchBarActiveWithContentPreview() = ElementThemedPreview {
|
|||
.background(color = Color.Blue)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
},
|
||||
resultHandler = {
|
||||
Text(
|
||||
text = "Results go here",
|
||||
modifier = Modifier
|
||||
.background(color = Color.Green)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
)
|
||||
) {
|
||||
Text(
|
||||
text = "Results go here",
|
||||
modifier = Modifier
|
||||
.background(color = Color.Green)
|
||||
.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalMaterial3Api::class)
|
||||
|
|
@ -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 = {},
|
||||
|
|
|
|||
|
|
@ -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(
|
|||
results = if (filteredSummaries.isNotEmpty()) {
|
||||
SearchBarResultState.Results(filteredSummaries)
|
||||
} else {
|
||||
SearchBarResultState.NoResults()
|
||||
SearchBarResultState.NoResultsFound()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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(),
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,3 +22,8 @@ data class UserSearchResult(
|
|||
val matrixUser: MatrixUser,
|
||||
val isUnresolved: Boolean = false,
|
||||
)
|
||||
|
||||
data class UserSearchResultState(
|
||||
val results: List<UserSearchResult>,
|
||||
val isSearching: Boolean,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -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,36 +37,45 @@ 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 (query.length >= MINIMUM_SEARCH_LENGTH) {
|
||||
// Debounce
|
||||
delay(DEBOUNCE_TIME_MILLIS)
|
||||
|
||||
val results = dataSource
|
||||
.search(query, MAXIMUM_SEARCH_RESULTS)
|
||||
.filter { !client.isMe(it.userId) }
|
||||
.map { UserSearchResult(it) }
|
||||
.toMutableList()
|
||||
|
||||
// If the query is another user's MXID and the result doesn't contain that user ID, query the profile information explicitly
|
||||
if (shouldQueryProfile && results.none { it.matrixUser.userId.value == query }) {
|
||||
results.add(
|
||||
0,
|
||||
dataSource.getProfile(UserId(query))
|
||||
?.let { UserSearchResult(it) }
|
||||
?: UserSearchResult(MatrixUser(UserId(query)), isUnresolved = true))
|
||||
}
|
||||
|
||||
if (shouldQueryProfile || shouldFetchSearchResults) {
|
||||
emit(UserSearchResultState(isSearching = shouldFetchSearchResults, results = listOfNotNull(fakeSearchResult)))
|
||||
}
|
||||
if (shouldFetchSearchResults) {
|
||||
val results = fetchSearchResults(query, shouldQueryProfile)
|
||||
emit(results)
|
||||
}
|
||||
}
|
||||
|
||||
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) }
|
||||
.map { UserSearchResult(it) }
|
||||
.toMutableList()
|
||||
|
||||
// If the query is another user's MXID and the result doesn't contain that user ID, query the profile information explicitly
|
||||
if (shouldQueryProfile && results.none { it.matrixUser.userId.value == query }) {
|
||||
results.add(
|
||||
0,
|
||||
dataSource.getProfile(UserId(query))
|
||||
?.let { UserSearchResult(it) }
|
||||
?: UserSearchResult(MatrixUser(UserId(query)), isUnresolved = true))
|
||||
}
|
||||
|
||||
return UserSearchResultState(results = results, isSearching = false)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEBOUNCE_TIME_MILLIS = 250L
|
||||
private const val MINIMUM_SEARCH_LENGTH = 3
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
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 {
|
|||
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 {
|
|||
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 {
|
|||
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 {
|
|||
|
||||
result.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem()).isEqualTo(searchResults.toUserSearchResults())
|
||||
assertThat(awaitItem().results).isEqualTo(searchResults.toUserSearchResults())
|
||||
awaitComplete()
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
|||
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 {
|
|||
|
||||
result.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem()).isEqualTo(listOf(placeholderResult(isUnresolved = true)) + searchResults.toUserSearchResults())
|
||||
assertThat(awaitItem().results).isEqualTo(listOf(placeholderResult(isUnresolved = true)) + searchResults.toUserSearchResults())
|
||||
awaitComplete()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
|||
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)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:4731f36d3ee1331c0767ad27d4286feca266deabfd55960a89b0e823925b30f2
|
||||
size 7646
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:ae2119fa8d67dc6787a3cf86ada1259a4ff941d1d9a55936d5ee84e71e42ac72
|
||||
size 7217
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:62baff22557bc9b130c87b64b45a53b0209390ecfe11d16e1b31868a5a264cf3
|
||||
size 31956
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
version https://git-lfs.github.com/spec/v1
|
||||
oid sha256:6b5645d81ef1ed73241ec2183f8d5b4cf4cd7510fc8804a26719842e8ed4bc7f
|
||||
size 29891
|
||||
Loading…
Add table
Add a link
Reference in a new issue