Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -367,14 +367,14 @@ public class ChannelListViewModel(
currentState: SearchMessageState,
channelFilter: FilterObject,
): SearchMessageState {
val offset = currentState.messages.size
val limit = channelLimit
logger.v { "[searchMessages] #$src; query: '${currentState.query}', offset: $offset, limit: $limit" }
val next = currentState.next
logger.v { "[searchMessages] #$src; query: '${currentState.query}', next: $next, limit: $limit" }
val result = chatClient.searchMessages(
channelFilter = channelFilter,
messageFilter = Filters.autocomplete("text", currentState.query),
offset = offset,
limit = limit,
next = next,
).await()
return when (result) {
is io.getstream.result.Result.Success -> {
Expand All @@ -383,15 +383,15 @@ public class ChannelListViewModel(
messages = currentState.messages + result.value.messages,
isLoading = false,
isLoadingMore = false,
canLoadMore = result.value.messages.size >= limit,
canLoadMore = !result.value.next.isNullOrEmpty(),
next = result.value.next,
)
}
is io.getstream.result.Result.Failure -> {
logger.e { "[searchMessages] #$src; failed: ${result.value}" }
currentState.copy(
isLoading = false,
isLoadingMore = false,
canLoadMore = true,
)
}
}
Expand Down Expand Up @@ -773,6 +773,7 @@ public class ChannelListViewModel(
private data class SearchMessageState(
val query: String = "",
val canLoadMore: Boolean = true,
val next: String? = null,
val messages: List<Message> = emptyList(),
val isLoading: Boolean = false,
val isLoadingMore: Boolean = false,
Expand All @@ -784,7 +785,8 @@ public class ChannelListViewModel(
"messages.size=${messages.size}, " +
"isLoading=$isLoading, " +
"isLoadingMore=$isLoadingMore, " +
"canLoadMore=$canLoadMore)"
"canLoadMore=$canLoadMore, " +
"next=$next)"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ package io.getstream.chat.android.compose.viewmodel.channels
import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.client.api.models.QueryChannelsRequest
import io.getstream.chat.android.client.channel.ChannelClient
import io.getstream.chat.android.client.persistance.repository.RepositoryFacade
import io.getstream.chat.android.client.setup.state.ClientState
import io.getstream.chat.android.compose.state.channels.list.ItemState
import io.getstream.chat.android.compose.state.channels.list.SearchQuery
Expand All @@ -30,10 +31,12 @@ import io.getstream.chat.android.models.FilterObject
import io.getstream.chat.android.models.Filters
import io.getstream.chat.android.models.InitializationState
import io.getstream.chat.android.models.OrFilterObject
import io.getstream.chat.android.models.SearchMessagesResult
import io.getstream.chat.android.models.TypingEvent
import io.getstream.chat.android.models.User
import io.getstream.chat.android.models.querysort.QuerySortByField
import io.getstream.chat.android.models.querysort.QuerySorter
import io.getstream.chat.android.randomMessage
import io.getstream.chat.android.state.event.handler.chat.factory.ChatEventHandlerFactory
import io.getstream.chat.android.state.plugin.internal.StatePlugin
import io.getstream.chat.android.state.plugin.state.StateRegistry
Expand All @@ -48,10 +51,15 @@ import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.TestScope
import kotlinx.coroutines.test.advanceUntilIdle
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.`should be equal to`
import org.junit.jupiter.api.Assertions.assertEquals
import org.junit.jupiter.api.Assertions.assertFalse
import org.junit.jupiter.api.Assertions.assertInstanceOf
import org.junit.jupiter.api.Assertions.assertNull
import org.junit.jupiter.api.Assertions.assertTrue
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.extension.ExtendWith
import org.mockito.kotlin.any
import org.mockito.kotlin.anyOrNull
import org.mockito.kotlin.argumentCaptor
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.eq
Expand All @@ -76,8 +84,8 @@ internal class ChannelListViewModelTest {
.get(this)

val channelsState = viewModel.channelsState
channelsState.channelItems.size `should be equal to` 0
channelsState.isLoading `should be equal to` true
assertEquals(0, channelsState.channelItems.size)
assertTrue(channelsState.isLoading)
}

@Test
Expand All @@ -95,8 +103,8 @@ internal class ChannelListViewModelTest {
.get(this)

val channelsState = viewModel.channelsState
channelsState.channelItems.size `should be equal to` 2
channelsState.isLoading `should be equal to` false
assertEquals(2, channelsState.channelItems.size)
assertFalse(channelsState.isLoading)
}

@Test
Expand All @@ -118,8 +126,8 @@ internal class ChannelListViewModelTest {
viewModel.performChannelAction(DeleteConversation(channel1))
viewModel.deleteConversation(channel1)

viewModel.activeChannelAction `should be equal to` null
viewModel.selectedChannel.value `should be equal to` null
assertNull(viewModel.activeChannelAction)
assertNull(viewModel.selectedChannel.value)
verify(channelClient).delete()
}

Expand All @@ -140,8 +148,8 @@ internal class ChannelListViewModelTest {
viewModel.selectChannel(channel1)
viewModel.muteChannel(channel1)

viewModel.activeChannelAction `should be equal to` null
viewModel.selectedChannel.value `should be equal to` null
assertNull(viewModel.activeChannelAction)
assertNull(viewModel.selectedChannel.value)
verify(chatClient).muteChannel("messaging", "channel1", null)
}

Expand Down Expand Up @@ -171,9 +179,9 @@ internal class ChannelListViewModelTest {
viewModel.selectChannel(channel1)
viewModel.unmuteChannel(channel1)

(viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState).isMuted `should be equal to` true
viewModel.activeChannelAction `should be equal to` null
viewModel.selectedChannel.value `should be equal to` null
assertTrue((viewModel.channelsState.channelItems.first() as ItemState.ChannelItemState).isMuted)
assertNull(viewModel.activeChannelAction)
assertNull(viewModel.selectedChannel.value)
verify(chatClient).unmuteChannel("messaging", "channel1")
}

Expand All @@ -193,8 +201,8 @@ internal class ChannelListViewModelTest {
viewModel.selectChannel(channel1)
viewModel.dismissChannelAction()

viewModel.activeChannelAction `should be equal to` null
viewModel.selectedChannel.value `should be equal to` null
assertNull(viewModel.activeChannelAction)
assertNull(viewModel.selectedChannel.value)
}

@Test
Expand Down Expand Up @@ -223,8 +231,8 @@ internal class ChannelListViewModelTest {

val captor = argumentCaptor<QueryChannelsRequest>()
verify(chatClient, times(2)).queryChannels(captor.capture())
captor.firstValue.offset `should be equal to` 0
captor.secondValue.offset `should be equal to` 30
assertEquals(0, captor.firstValue.offset)
assertEquals(30, captor.secondValue.offset)
}

@Test
Expand All @@ -246,7 +254,7 @@ internal class ChannelListViewModelTest {

val captor = argumentCaptor<QueryChannelsRequest>()
verify(chatClient, times(1)).queryChannels(captor.capture())
captor.firstValue.offset `should be equal to` 0
assertEquals(0, captor.firstValue.offset)
}

@Test
Expand All @@ -271,8 +279,8 @@ internal class ChannelListViewModelTest {
val andFilterObject = captor.secondValue.filter as AndFilterObject
val orFilterObject = andFilterObject.filterObjects.last() as OrFilterObject
val autoCompleteFilterObject = orFilterObject.filterObjects.last() as AutocompleteFilterObject
autoCompleteFilterObject.fieldName `should be equal to` "name"
autoCompleteFilterObject.value `should be equal to` "Search query"
assertEquals("name", autoCompleteFilterObject.fieldName)
assertEquals("Search query", autoCompleteFilterObject.value)
}

@Test
Expand Down Expand Up @@ -301,8 +309,8 @@ internal class ChannelListViewModelTest {
val andFilterObject = captor.secondValue.filter as AndFilterObject
val orFilterObject = andFilterObject.filterObjects.last() as OrFilterObject
val autoCompleteFilterObject = orFilterObject.filterObjects.last() as AutocompleteFilterObject
autoCompleteFilterObject.fieldName `should be equal to` "name"
autoCompleteFilterObject.value `should be equal to` "Search query"
assertEquals("name", autoCompleteFilterObject.fieldName)
assertEquals("Search query", autoCompleteFilterObject.value)
}

@Test
Expand Down Expand Up @@ -333,9 +341,125 @@ internal class ChannelListViewModelTest {

val captor = argumentCaptor<QueryChannelsRequest>()
verify(chatClient, times(2)).queryChannels(captor.capture())
captor.allValues.size `should be equal to` 2
captor.firstValue.offset `should be equal to` 0
captor.secondValue.offset `should be equal to` 30
assertEquals(2, captor.allValues.size)
assertEquals(0, captor.firstValue.offset)
assertEquals(30, captor.secondValue.offset)
}

@Test
fun `Given channel list When setting message search query Should search messages without offset or cursor`() =
runTest {
val chatClient: ChatClient = mock()
val messages = listOf(randomMessage(cid = "messaging:channel1"))
val searchResult = SearchMessagesResult(messages = messages, next = "cursor_page2")
val viewModel = Fixture(chatClient)
.givenCurrentUser()
.givenChannelsQuery()
.givenChannelsState(
channelsStateData = ChannelsStateData.Result(listOf(channel1)),
loading = false,
)
.givenChannelMutes()
.givenSearchMessagesResult(searchResult)
.givenRepositorySelectChannels(listOf(channel1))
.get(this)

viewModel.setSearchQuery(SearchQuery.Messages("hello"))
advanceUntilIdle()

verify(chatClient).searchMessages(
channelFilter = any(),
messageFilter = any(),
offset = eq(null),
limit = any(),
next = eq(null),
sort = eq(null),
)
val items = viewModel.channelsState.channelItems
assertEquals(1, items.size)
assertInstanceOf(ItemState.SearchResultItemState::class.java, items.first())
}

@Test
fun `Given message search results with next cursor When loading more Should pass the cursor`() =
runTest {
val chatClient: ChatClient = mock()
val firstPageMessages = listOf(randomMessage(cid = "messaging:channel1"))
val firstPageResult = SearchMessagesResult(messages = firstPageMessages, next = "cursor_page2")
val secondPageMessages = listOf(randomMessage(cid = "messaging:channel1"))
val secondPageResult = SearchMessagesResult(messages = secondPageMessages, next = null)

whenever(
chatClient.searchMessages(any(), any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()),
).doReturn(
firstPageResult.asCall(),
secondPageResult.asCall(),
)

val viewModel = Fixture(chatClient)
.givenCurrentUser()
.givenChannelsQuery()
.givenChannelsState(
channelsStateData = ChannelsStateData.Result(listOf(channel1)),
loading = false,
)
.givenChannelMutes()
.givenRepositorySelectChannels(listOf(channel1))
.get(this)

viewModel.setSearchQuery(SearchQuery.Messages("hello"))
advanceUntilIdle()

viewModel.loadMore()
advanceUntilIdle()

val captor = argumentCaptor<String>()
verify(chatClient, times(2)).searchMessages(
channelFilter = any(),
messageFilter = any(),
offset = anyOrNull(),
limit = anyOrNull(),
next = captor.capture(),
sort = anyOrNull(),
)
assertNull(captor.firstValue)
assertEquals("cursor_page2", captor.secondValue)
}

@Test
fun `Given message search results without next cursor When loading more Should not load more`() =
runTest {
val chatClient: ChatClient = mock()
val messages = listOf(randomMessage(cid = "messaging:channel1"))
val searchResult = SearchMessagesResult(messages = messages, next = null)
val viewModel = Fixture(chatClient)
.givenCurrentUser()
.givenChannelsQuery()
.givenChannelsState(
channelsStateData = ChannelsStateData.Result(listOf(channel1)),
loading = false,
)
.givenChannelMutes()
.givenSearchMessagesResult(searchResult)
.givenRepositorySelectChannels(listOf(channel1))
.get(this)

viewModel.setSearchQuery(SearchQuery.Messages("hello"))
advanceUntilIdle()

assertTrue(viewModel.channelsState.endOfChannels)

viewModel.loadMore()
advanceUntilIdle()

verify(chatClient, times(1)).searchMessages(
channelFilter = any(),
messageFilter = any(),
offset = anyOrNull(),
limit = anyOrNull(),
next = anyOrNull(),
sort = anyOrNull(),
)
}

private class Fixture(
Expand All @@ -347,6 +471,7 @@ internal class ChannelListViewModelTest {
private val clientState: ClientState = mock()
private val stateRegistry: StateRegistry = mock()
private val globalState: GlobalState = mock()
private val repositoryFacade: RepositoryFacade = mock()

init {
val statePlugin: StatePlugin = mock()
Expand All @@ -357,6 +482,7 @@ internal class ChannelListViewModelTest {
whenever(chatClient.channel(any())) doReturn channelClient
whenever(chatClient.channel(any(), any())) doReturn channelClient
whenever(chatClient.clientState) doReturn clientState
whenever(chatClient.repositoryFacade) doReturn repositoryFacade
whenever(globalState.channelDraftMessages) doReturn MutableStateFlow(emptyMap())
}

Expand Down Expand Up @@ -394,6 +520,16 @@ internal class ChannelListViewModelTest {
whenever(chatClient.unmuteChannel(any(), any())) doReturn Unit.asCall()
}

fun givenSearchMessagesResult(result: SearchMessagesResult) = apply {
whenever(
chatClient.searchMessages(any(), any(), anyOrNull(), anyOrNull(), anyOrNull(), anyOrNull()),
) doReturn result.asCall()
}

suspend fun givenRepositorySelectChannels(channels: List<Channel> = emptyList()) = apply {
whenever(repositoryFacade.selectChannels(any<List<String>>())) doReturn channels
}

fun givenChannelsState(
channelsStateData: ChannelsStateData = ChannelsStateData.Loading,
channels: List<Channel>? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5209,15 +5209,15 @@ public final class io/getstream/chat/android/ui/viewmodel/search/SearchViewModel

public final class io/getstream/chat/android/ui/viewmodel/search/SearchViewModel$State {
public fun <init> ()V
public fun <init> (Ljava/lang/String;ZLjava/util/List;ZZ)V
public synthetic fun <init> (Ljava/lang/String;ZLjava/util/List;ZZILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun <init> (Ljava/lang/String;ZLjava/util/List;ZZLjava/lang/String;)V
public synthetic fun <init> (Ljava/lang/String;ZLjava/util/List;ZZLjava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()Ljava/lang/String;
public final fun component2 ()Z
public final fun component3 ()Ljava/util/List;
public final fun component4 ()Z
public final fun component5 ()Z
public final fun copy (Ljava/lang/String;ZLjava/util/List;ZZ)Lio/getstream/chat/android/ui/viewmodel/search/SearchViewModel$State;
public static synthetic fun copy$default (Lio/getstream/chat/android/ui/viewmodel/search/SearchViewModel$State;Ljava/lang/String;ZLjava/util/List;ZZILjava/lang/Object;)Lio/getstream/chat/android/ui/viewmodel/search/SearchViewModel$State;
public final fun copy (Ljava/lang/String;ZLjava/util/List;ZZLjava/lang/String;)Lio/getstream/chat/android/ui/viewmodel/search/SearchViewModel$State;
public static synthetic fun copy$default (Lio/getstream/chat/android/ui/viewmodel/search/SearchViewModel$State;Ljava/lang/String;ZLjava/util/List;ZZLjava/lang/String;ILjava/lang/Object;)Lio/getstream/chat/android/ui/viewmodel/search/SearchViewModel$State;
public fun equals (Ljava/lang/Object;)Z
public final fun getCanLoadMore ()Z
public final fun getQuery ()Ljava/lang/String;
Expand Down
Loading
Loading