Skip to content
Merged
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 @@ -19,6 +19,7 @@ package io.getstream.chat.android.compose.tests
import io.getstream.chat.android.compose.robots.assertChannelCount
import io.getstream.chat.android.compose.robots.assertChannelWithName
import io.getstream.chat.android.compose.sample.ui.InitTestActivity
import io.getstream.chat.android.e2e.test.robots.ParticipantRobot
import io.qameta.allure.kotlin.Allure.step
import io.qameta.allure.kotlin.AllureId
import org.junit.Test
Expand Down Expand Up @@ -57,4 +58,24 @@ class ChannelSearchTests : StreamTestCase() {
.assertChannelWithName(channelName)
}
}

@AllureId("12487")
@Test
fun test_userSearchesForMemberName_noChannelIsFound() {
step("GIVEN channels and a direct message channel with the participant exist") {
backendRobot.generateChannels(channelsCount = 3, withDirectMessageChannel = true)
}
step("AND user logs in") {
userRobot
.login()
.waitForChannelListToLoad()
.assertChannelCount(4)
}
step("WHEN user searches for the participant's name") {
userRobot.search(ParticipantRobot.name.substringBefore(" "))
}
step("THEN no channel is shown, because the default search matches channel names only") {
userRobot.assertChannelCount(0)
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
/*
* Copyright (c) 2014-2026 Stream.io Inc. All rights reserved.
*
* Licensed under the Stream License;
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://github.com/GetStream/stream-chat-android/blob/main/LICENSE
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package io.getstream.chat.android.compose.tests

import io.getstream.chat.android.compose.robots.assertChannelCount
import io.getstream.chat.android.compose.robots.assertChannelWithName
import io.getstream.chat.android.compose.sample.ui.InitTestActivity
import io.getstream.chat.android.e2e.test.robots.ParticipantRobot
import io.qameta.allure.kotlin.Allure.step
import io.qameta.allure.kotlin.AllureId
import org.junit.Test

/**
* Covers a channel search customized through `ChannelListSearchOptions`. It needs the app started with the channel
* search and member name search flags, which make the sample also match channels by the name of a member. The
* default channel search is covered by [ChannelSearchTests].
*/
class MemberNameSearchTests : StreamTestCase() {

override fun initTestActivity() = InitTestActivity.UserLogin
override var useChannelSearch = true
override var useMemberNameSearch = true

@AllureId("12485")
@Test
fun test_userSearchesForChannelsByMemberName() {
step("GIVEN group channels with a third member and a direct message channel without them exist") {
backendRobot.generateChannels(channelsCount = 3, withDirectMessageChannel = true)
}
step("AND user logs in") {
userRobot
.login()
.waitForChannelListToLoad()
.assertChannelCount(4)
}
step("WHEN user searches for the third member's name") {
userRobot.search(MEMBER_NAME_QUERY)
}
step("THEN only the channels with that member are shown") {
userRobot
.assertChannelCount(3)
.assertChannelWithName(ParticipantRobot.name, isDisplayed = false)
}
}

@AllureId("12486")
@Test
fun test_userSearchesForChannelByNameWithMemberNameSearch() {
step("GIVEN channels exist, one with a searchable name") {
backendRobot.generateChannels(
channelsCount = 3,
channelNames = listOf(CHANNEL_NAME),
withDirectMessageChannel = true,
)
}
step("AND user logs in") {
userRobot
.login()
.waitForChannelListToLoad()
.assertChannelCount(4)
}
step("WHEN user searches for the channel by its name") {
userRobot.search(CHANNEL_NAME_QUERY)
}
step("THEN only the matching channel is shown") {
userRobot
.assertChannelCount(1)
.assertChannelWithName(CHANNEL_NAME)
}
}

private companion object {
/** A member of the seeded group channels only: not the app user, and not part of the direct message channel. */
const val MEMBER_NAME_QUERY = "Lando"
const val CHANNEL_NAME = "Blue Team"
const val CHANNEL_NAME_QUERY = "Blue"
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,9 @@ abstract class StreamTestCase {

/** Makes the channel list header search for channels instead of messages. */
open var useChannelSearch = false

/** Makes the channel search also match channels by the name of a member. */
open var useMemberNameSearch = false
val userRobot = UserRobot()
lateinit var backendRobot: BackendRobot
lateinit var participantRobot: ParticipantRobot
Expand Down Expand Up @@ -105,6 +108,7 @@ abstract class StreamTestCase {
it.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TASK)
it.putExtra("InitTestActivity", initTestActivity())
it.putExtra("CHANNEL_SEARCH", useChannelSearch)
it.putExtra("MEMBER_NAME_SEARCH", useMemberNameSearch)
if (useMockServer) {
it.putExtra("BASE_URL", mockServer.url)
it.putExtra("USE_V2_WEBSOCKET", apiVersion == "v2")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,9 @@ class StartupActivity : AppCompatActivity() {
if (intent.hasExtra("CHANNEL_SEARCH")) {
customSettings().isChannelSearchEnabled = intent.getBooleanExtra("CHANNEL_SEARCH", false)
}
if (intent.hasExtra("MEMBER_NAME_SEARCH")) {
customSettings().isMemberNameSearchEnabled = intent.getBooleanExtra("MEMBER_NAME_SEARCH", false)
}

val initTestActivity = intent.getSerializableExtra("InitTestActivity") as? InitTestActivity
if (initTestActivity != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class CustomSettings(private val context: Context) {
var isLocationSharingEnabled: Boolean by booleanPref(LocationSharing)
var isLocalUnreadCountEnabled: Boolean by booleanPref(LocalUnreadCount)
var isChannelSearchEnabled: Boolean by booleanPref(ChannelSearch)
var isMemberNameSearchEnabled: Boolean by booleanPref(MemberNameSearch)
var isMarkdownEnabled: Boolean by booleanPref(Markdown)

private fun booleanPref(key: String, default: Boolean = false) =
Expand All @@ -60,6 +61,7 @@ private const val SystemAttachmentPicker = "system_attachment_picker"
private const val LocationSharing = "location_sharing"
private const val LocalUnreadCount = "local_unread_count"
private const val ChannelSearch = "channel_search"
private const val MemberNameSearch = "member_name_search"
private const val Markdown = "markdown"

fun Context.customSettings(): CustomSettings = CustomSettings(this)
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@ package io.getstream.chat.android.compose.sample.feature.channel.list

import io.getstream.chat.android.client.ChatClient
import io.getstream.chat.android.compose.sample.data.CustomSettings
import io.getstream.chat.android.compose.viewmodel.channels.ChannelListSearchOptions
import io.getstream.chat.android.compose.viewmodel.channels.ChannelListViewModelFactory
import io.getstream.chat.android.models.Channel
import io.getstream.chat.android.models.FilterObject
import io.getstream.chat.android.models.Filters
import io.getstream.chat.android.models.querysort.QuerySortByField

Expand All @@ -30,10 +32,17 @@ import io.getstream.chat.android.models.querysort.QuerySortByField
* to make the feature testable. Otherwise, the predefined server-side filter is used, which resolves
* to: messaging channels the current user is a member of, without a draft, sorted by "pinned_at" and
* "last_updated" descending.
*
* When member name search is enabled, the channel search also matches channels by the name of a member.
*/
fun sampleChannelListViewModelFactory(settings: CustomSettings): ChannelListViewModelFactory {
val chatClient = ChatClient.instance()
val currentUserId = chatClient.getCurrentUser()?.id.orEmpty()
val searchOptions = if (settings.isMemberNameSearchEnabled) {
MemberNameSearchOptions()
} else {
ChannelListSearchOptions()
}
return if (settings.isLocalUnreadCountEnabled) {
ChannelListViewModelFactory(
chatClient = chatClient,
Expand All @@ -44,6 +53,7 @@ fun sampleChannelListViewModelFactory(settings: CustomSettings): ChannelListView
Filters.or(Filters.notExists("draft"), Filters.eq("draft", false)),
),
chatEventHandlerFactory = CustomChatEventHandlerFactory(),
searchOptions = searchOptions,
)
} else {
ChannelListViewModelFactory(
Expand All @@ -54,6 +64,24 @@ fun sampleChannelListViewModelFactory(settings: CustomSettings): ChannelListView
"user_id" to currentUserId,
),
chatEventHandlerFactory = CustomChatEventHandlerFactory(),
searchOptions = searchOptions,
)
}
}

/**
* Searches channels by their name or by the name of one of their members, among the channels the current user is a
* member of.
*/
private class MemberNameSearchOptions : ChannelListSearchOptions() {
override fun channelFilter(params: ChannelSearchParams): FilterObject {
val searchFilter = Filters.and(
Filters.or(
Filters.autocomplete("name", params.query),
Filters.autocomplete("member.user.name", params.query),
),
Filters.`in`("members", listOfNotNull(params.currentUser?.id)),
)
return params.listFilter?.let { Filters.and(it, searchFilter) } ?: searchFilter
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,9 @@ class CustomLoginActivity : AppCompatActivity() {
var isChannelSearchEnabled by remember {
mutableStateOf(settings.isChannelSearchEnabled)
}
var isMemberNameSearchEnabled by remember {
mutableStateOf(settings.isMemberNameSearchEnabled)
}
var isMarkdownEnabled by remember {
mutableStateOf(settings.isMarkdownEnabled)
}
Expand Down Expand Up @@ -236,6 +239,15 @@ class CustomLoginActivity : AppCompatActivity() {
settings.isChannelSearchEnabled = it
},
),
FeatureFlag(
label = stringResource(R.string.custom_login_flag_member_name_search_label),
description = stringResource(R.string.custom_login_flag_member_name_search_description),
value = isMemberNameSearchEnabled,
onValueChange = {
isMemberNameSearchEnabled = it
settings.isMemberNameSearchEnabled = it
},
),
FeatureFlag(
label = stringResource(R.string.custom_login_flag_markdown_label),
description = stringResource(R.string.custom_login_flag_markdown_description),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@
<string name="custom_login_flag_local_unread_count_description">Track the unread count locally for channels with read events disabled. Also shows livestream channels in the channel list</string>
<string name="custom_login_flag_channel_search_label">Channel search</string>
<string name="custom_login_flag_channel_search_description">Search for channels instead of messages from the channel list header</string>
<string name="custom_login_flag_member_name_search_label">Member name search</string>
<string name="custom_login_flag_member_name_search_description">Also find channels by the name of a member when searching for channels</string>
<string name="custom_login_flag_markdown_label">Markdown</string>
<string name="custom_login_flag_markdown_description">Render message text as markdown</string>

Expand Down
Loading
Loading