From 35b12f265f82df5583b26235a4b1196693da1e67 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 28 Mar 2024 17:03:34 +0100 Subject: [PATCH] Room directory : add tests and cleanup --- .idea/kotlinc.xml | 2 +- .../api/RoomDirectoryEntryPoint.kt | 2 - features/roomdirectory/impl/build.gradle.kts | 9 + .../impl/DefaultRoomDirectoryEntryPoint.kt | 2 - .../impl/root/RoomDirectoryNode.kt | 1 - .../impl/root/RoomDirectoryPresenter.kt | 11 +- .../impl/root/RoomDirectoryStateProvider.kt | 63 +++--- .../impl/root/RoomDirectoryView.kt | 48 ++--- .../roomdirectory/impl/root/di/JoinRoom.kt | 32 +++ .../impl/root/model/RoomDirectoryListState.kt | 1 - .../roomdirectory/impl/root/FakeJoinRoom.kt | 26 +++ .../impl/root/RoomDirectoryPresenterTest.kt | 182 ++++++++++++++++++ .../impl/root/RoomDirectoryViewTest.kt | 112 +++++++++++ .../roomlist/impl/RoomListViewTest.kt | 2 + .../search/RoomListSearchPresenterTests.kt | 22 ++- .../libraries/matrix/impl/RustMatrixClient.kt | 3 +- .../roomdirectory/RoomDescriptionMapper.kt | 1 - .../RoomDirectorySearchProcessor.kt | 1 - .../roomdirectory/RustRoomDirectoryList.kt | 1 - .../roomdirectory/RustRoomDirectoryService.kt | 1 - .../libraries/matrix/test/FakeMatrixClient.kt | 5 + .../roomdirectory/FakeRoomDirectoryList.kt | 31 +++ .../roomdirectory/FakeRoomDirectoryService.kt | 8 +- .../roomdirectory/RoomDescriptionFixture.kt | 41 ++++ .../android/libraries/testtags/TestTags.kt | 5 + .../android/samples/minimal/RoomListScreen.kt | 4 +- .../android/tests/testutils/PresenterTest.kt | 34 ++++ 27 files changed, 573 insertions(+), 77 deletions(-) create mode 100644 features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/di/JoinRoom.kt create mode 100644 features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/FakeJoinRoom.kt create mode 100644 features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt create mode 100644 features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt create mode 100644 libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt create mode 100644 tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml index 8d81632f83..fe63bb677d 100644 --- a/.idea/kotlinc.xml +++ b/.idea/kotlinc.xml @@ -1,6 +1,6 @@ - \ No newline at end of file diff --git a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt index 8fbf342697..5a693a4a83 100644 --- a/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/api/src/main/kotlin/io/element/android/features/roomdirectory/api/RoomDirectoryEntryPoint.kt @@ -23,7 +23,6 @@ import io.element.android.libraries.architecture.FeatureEntryPoint import io.element.android.libraries.matrix.api.core.RoomId interface RoomDirectoryEntryPoint : FeatureEntryPoint { - fun nodeBuilder(parentNode: Node, buildContext: BuildContext): NodeBuilder interface NodeBuilder { @@ -35,4 +34,3 @@ interface RoomDirectoryEntryPoint : FeatureEntryPoint { fun onOpenRoom(roomId: RoomId) } } - diff --git a/features/roomdirectory/impl/build.gradle.kts b/features/roomdirectory/impl/build.gradle.kts index 9d56802700..d26a75ca8c 100644 --- a/features/roomdirectory/impl/build.gradle.kts +++ b/features/roomdirectory/impl/build.gradle.kts @@ -25,6 +25,11 @@ plugins { android { namespace = "io.element.android.features.roomdirectory.impl" + testOptions { + unitTests { + isIncludeAndroidResources = true + } + } } anvil { @@ -41,13 +46,17 @@ dependencies { implementation(projects.libraries.matrixui) implementation(projects.libraries.designsystem) implementation(projects.libraries.uiStrings) + implementation(projects.libraries.testtags) testImplementation(libs.test.junit) + testImplementation(libs.androidx.compose.ui.test.junit) + testImplementation(libs.test.robolectric) testImplementation(libs.coroutines.test) testImplementation(libs.molecule.runtime) testImplementation(libs.test.truth) testImplementation(libs.test.turbine) testImplementation(projects.libraries.matrix.test) + testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt index 11b5b1b27d..c15a748a9e 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/DefaultRoomDirectoryEntryPoint.kt @@ -28,12 +28,10 @@ import javax.inject.Inject @ContributesBinding(AppScope::class) class DefaultRoomDirectoryEntryPoint @Inject constructor() : RoomDirectoryEntryPoint { - override fun nodeBuilder(parentNode: Node, buildContext: BuildContext): RoomDirectoryEntryPoint.NodeBuilder { val plugins = ArrayList() return object : RoomDirectoryEntryPoint.NodeBuilder { - override fun callback(callback: RoomDirectoryEntryPoint.Callback): RoomDirectoryEntryPoint.NodeBuilder { plugins += callback return this diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt index c99d30640a..dc3581589e 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryNode.kt @@ -35,7 +35,6 @@ class RoomDirectoryNode @AssistedInject constructor( @Assisted plugins: List, private val presenter: RoomDirectoryPresenter, ) : Node(buildContext, plugins = plugins) { - private fun onRoomJoined(roomId: RoomId) { plugins().forEach { it.onOpenRoom(roomId) diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt index 2247a82b55..95cfa8166b 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenter.kt @@ -26,13 +26,13 @@ import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.runtime.setValue +import io.element.android.features.roomdirectory.impl.root.di.JoinRoom import io.element.android.features.roomdirectory.impl.root.model.RoomDirectoryListState import io.element.android.features.roomdirectory.impl.root.model.toFeatureModel import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.architecture.runUpdatingState import io.element.android.libraries.core.coroutine.CoroutineDispatchers -import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService @@ -46,10 +46,9 @@ import javax.inject.Inject class RoomDirectoryPresenter @Inject constructor( private val dispatchers: CoroutineDispatchers, - private val matrixClient: MatrixClient, + private val joinRoom: JoinRoom, private val roomDirectoryService: RoomDirectoryService, ) : Presenter { - @Composable override fun present(): RoomDirectoryState { var loadingMore by remember { @@ -68,9 +67,9 @@ class RoomDirectoryPresenter @Inject constructor( } LaunchedEffect(searchQuery) { if (searchQuery == null) return@LaunchedEffect - //debounce search query + // debounce search query delay(300) - //cancel load more right away + // cancel load more right away loadingMore = false roomDirectoryList.filter(searchQuery, 20) } @@ -108,7 +107,7 @@ class RoomDirectoryPresenter @Inject constructor( private fun CoroutineScope.joinRoom(state: MutableState>, roomId: RoomId) = launch { state.runUpdatingState { - matrixClient.joinRoom(roomId) + joinRoom(roomId) } } diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt index ff12bc7ee6..3fa7877b6f 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryStateProvider.kt @@ -25,39 +25,14 @@ import io.element.android.libraries.matrix.api.core.RoomId import kotlinx.collections.immutable.ImmutableList import kotlinx.collections.immutable.persistentListOf -open class RoomDirectorySearchStateProvider : PreviewParameterProvider { +open class RoomDirectoryStateProvider : PreviewParameterProvider { override val values: Sequence get() = sequenceOf( aRoomDirectoryState(), aRoomDirectoryState( query = "Element", - roomDescriptions = persistentListOf( - RoomDescription( - roomId = RoomId("@exa:matrix.org"), - name = "Element X Android", - description = "Element X is a secure, private and decentralized messenger.", - avatarData = AvatarData( - id = "@exa:matrix.org", - name = "Element X Android", - url = null, - size = AvatarSize.RoomDirectoryItem - ), - canBeJoined = true, - ), - RoomDescription( - roomId = RoomId("@exi:matrix.org"), - name = "Element X iOS", - description = "Element X is a secure, private and decentralized messenger.", - avatarData = AvatarData( - id = "@exi:matrix.org", - name = "Element X iOS", - url = null, - size = AvatarSize.RoomDirectoryItem - ), - canBeJoined = false, - ) - ) - ), + roomDescriptions = aRoomDescriptionList(), + ) ) } @@ -66,10 +41,40 @@ fun aRoomDirectoryState( displayLoadMoreIndicator: Boolean = false, roomDescriptions: ImmutableList = persistentListOf(), joinRoomAction: AsyncAction = AsyncAction.Uninitialized, + eventSink: (RoomDirectoryEvents) -> Unit = {}, ) = RoomDirectoryState( query = query, roomDescriptions = roomDescriptions, displayLoadMoreIndicator = displayLoadMoreIndicator, joinRoomAction = joinRoomAction, - eventSink = {}, + eventSink = eventSink, ) + +fun aRoomDescriptionList(): ImmutableList { + return persistentListOf( + RoomDescription( + roomId = RoomId("!exa:matrix.org"), + name = "Element X Android", + description = "Element X is a secure, private and decentralized messenger.", + avatarData = AvatarData( + id = "!exa:matrix.org", + name = "Element X Android", + url = null, + size = AvatarSize.RoomDirectoryItem + ), + canBeJoined = true, + ), + RoomDescription( + roomId = RoomId("!exi:matrix.org"), + name = "Element X iOS", + description = "Element X is a secure, private and decentralized messenger.", + avatarData = AvatarData( + id = "!exi:matrix.org", + name = "Element X iOS", + url = null, + size = AvatarSize.RoomDirectoryItem + ), + canBeJoined = false, + ) + ) +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt index 6b5d94eb63..a40855fcc3 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryView.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalFocusManager +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.tooling.preview.PreviewParameter @@ -61,6 +62,7 @@ import io.element.android.libraries.designsystem.theme.components.Text import io.element.android.libraries.designsystem.theme.components.TextField import io.element.android.libraries.designsystem.theme.components.TopAppBar import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.testtags.TestTags import io.element.android.libraries.ui.strings.CommonStrings import kotlinx.collections.immutable.ImmutableList @@ -71,7 +73,6 @@ fun RoomDirectoryView( onBackPressed: () -> Unit, modifier: Modifier = Modifier, ) { - fun joinRoom(roomId: RoomId) { state.eventSink(RoomDirectoryEvents.JoinRoom(roomId)) } @@ -86,8 +87,8 @@ fun RoomDirectoryView( state = state, onResultClicked = ::joinRoom, modifier = Modifier - .padding(padding) - .consumeWindowInsets(padding) + .padding(padding) + .consumeWindowInsets(padding) ) } ) @@ -96,7 +97,8 @@ fun RoomDirectoryView( onSuccess = onRoomJoined, onErrorDismiss = { state.eventSink(RoomDirectoryEvents.JoinRoomDismissError) - }) + } + ) } @OptIn(ExperimentalMaterial3Api::class) @@ -171,7 +173,7 @@ private fun RoomDirectoryRoomList( if (displayLoadMoreIndicator) { item { LoadMoreIndicator(modifier = Modifier.fillMaxWidth()) - LaunchedEffect(Unit) { + LaunchedEffect(onReachedLoadMore) { onReachedLoadMore() } } @@ -182,10 +184,10 @@ private fun RoomDirectoryRoomList( @Composable private fun LoadMoreIndicator(modifier: Modifier = Modifier) { Box( - modifier - .fillMaxWidth() - .wrapContentHeight() - .padding(24.dp), + modifier + .fillMaxWidth() + .wrapContentHeight() + .padding(24.dp), contentAlignment = Alignment.Center, ) { CircularProgressIndicator( @@ -213,7 +215,7 @@ private fun SearchTextField( ) { val focusManager = LocalFocusManager.current TextField( - modifier = modifier, + modifier = modifier.testTag(TestTags.searchTextField.value), textStyle = ElementTheme.typography.fontBodyLgRegular, singleLine = true, value = query, @@ -255,14 +257,14 @@ private fun RoomDirectoryRoomRow( ) { Row( modifier = modifier - .fillMaxWidth() - .clickable { onClick(roomDescription.roomId) } - .padding( - top = 12.dp, - bottom = 12.dp, - start = 16.dp, - ) - .height(IntrinsicSize.Min), + .fillMaxWidth() + .clickable { onClick(roomDescription.roomId) } + .padding( + top = 12.dp, + bottom = 12.dp, + start = 16.dp, + ) + .height(IntrinsicSize.Min), ) { Avatar( avatarData = roomDescription.avatarData, @@ -270,8 +272,8 @@ private fun RoomDirectoryRoomRow( ) Column( modifier = Modifier - .weight(1f) - .padding(start = 16.dp) + .weight(1f) + .padding(start = 16.dp) ) { Text( text = roomDescription.name, @@ -293,8 +295,8 @@ private fun RoomDirectoryRoomRow( text = stringResource(id = CommonStrings.action_join), color = ElementTheme.colors.textSuccessPrimary, modifier = Modifier - .align(Alignment.CenterVertically) - .padding(start = 4.dp, end = 12.dp) + .align(Alignment.CenterVertically) + .padding(start = 4.dp, end = 12.dp) ) } else { Spacer(modifier = Modifier.width(24.dp)) @@ -304,7 +306,7 @@ private fun RoomDirectoryRoomRow( @PreviewsDayNight @Composable -fun RoomDirectorySearchViewLightPreview(@PreviewParameter(RoomDirectorySearchStateProvider::class) state: RoomDirectoryState) = ElementPreview { +internal fun RoomDirectoryViewPreview(@PreviewParameter(RoomDirectoryStateProvider::class) state: RoomDirectoryState) = ElementPreview { RoomDirectoryView( state = state, onRoomJoined = {}, diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/di/JoinRoom.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/di/JoinRoom.kt new file mode 100644 index 0000000000..983d2a1dd2 --- /dev/null +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/di/JoinRoom.kt @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.element.android.features.roomdirectory.impl.root.di + +import com.squareup.anvil.annotations.ContributesBinding +import io.element.android.libraries.di.SessionScope +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import javax.inject.Inject + +interface JoinRoom { + suspend operator fun invoke(roomId: RoomId): Result +} + +@ContributesBinding(SessionScope::class) +class DefaultJoinRoom @Inject constructor(private val client: MatrixClient) : JoinRoom { + override suspend fun invoke(roomId: RoomId) = client.joinRoom(roomId) +} diff --git a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt index d85295ca7e..60f344f67b 100644 --- a/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt +++ b/features/roomdirectory/impl/src/main/kotlin/io/element/android/features/roomdirectory/impl/root/model/RoomDirectoryListState.kt @@ -24,7 +24,6 @@ internal data class RoomDirectoryListState( val hasMoreToLoad: Boolean, val items: ImmutableList, ) { - companion object { val Default = RoomDirectoryListState( hasMoreToLoad = true, diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/FakeJoinRoom.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/FakeJoinRoom.kt new file mode 100644 index 0000000000..3f4d17aefd --- /dev/null +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/FakeJoinRoom.kt @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.element.android.features.roomdirectory.impl.root + +import io.element.android.features.roomdirectory.impl.root.di.JoinRoom +import io.element.android.libraries.matrix.api.core.RoomId + +class FakeJoinRoom( + var lambda: (RoomId) -> Result = { Result.success(it) } +) : JoinRoom { + override suspend fun invoke(roomId: RoomId) = lambda(roomId) +} diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt new file mode 100644 index 0000000000..eefafc86e1 --- /dev/null +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryPresenterTest.kt @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.element.android.features.roomdirectory.impl.root + +import com.google.common.truth.Truth.assertThat +import io.element.android.features.roomdirectory.impl.root.di.JoinRoom +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.libraries.matrix.test.roomdirectory.FakeRoomDirectoryList +import io.element.android.libraries.matrix.test.roomdirectory.FakeRoomDirectoryService +import io.element.android.libraries.matrix.test.roomdirectory.aRoomDescription +import io.element.android.tests.testutils.lambda.any +import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value +import io.element.android.tests.testutils.test +import io.element.android.tests.testutils.testCoroutineDispatchers +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.advanceUntilIdle +import kotlinx.coroutines.test.runTest +import org.junit.Test + +@OptIn(ExperimentalCoroutinesApi::class) class RoomDirectoryPresenterTest { + @Test + fun `present - initial state`() = runTest { + val presenter = createRoomDirectoryPresenter() + presenter.test { + val initialState = awaitItem() + assertThat(initialState.query).isEmpty() + assertThat(initialState.displayEmptyState).isFalse() + assertThat(initialState.joinRoomAction).isEqualTo(AsyncAction.Uninitialized) + assertThat(initialState.roomDescriptions).isEmpty() + assertThat(initialState.displayLoadMoreIndicator).isTrue() + } + } + + @Test + fun `present - room directory list emits empty state`() = runTest { + val directoryListStateFlow = MutableSharedFlow(replay = 1) + val roomDirectoryList = FakeRoomDirectoryList(directoryListStateFlow) + val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } + val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) + presenter.test { + skipItems(1) + directoryListStateFlow.emit( + RoomDirectoryList.State(false, emptyList()) + ) + awaitItem().also { state -> + assertThat(state.displayEmptyState).isTrue() + } + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - room directory list emits non-empty state`() = runTest { + val directoryListStateFlow = MutableSharedFlow(replay = 1) + val roomDirectoryList = FakeRoomDirectoryList(directoryListStateFlow) + val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } + val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) + presenter.test { + skipItems(1) + directoryListStateFlow.emit( + RoomDirectoryList.State( + hasMoreToLoad = true, + items = listOf(aRoomDescription()) + ) + ) + awaitItem().also { state -> + assertThat(state.displayEmptyState).isFalse() + assertThat(state.roomDescriptions).hasSize(1) + } + cancelAndIgnoreRemainingEvents() + } + } + + @Test + fun `present - emit search event`() = runTest { + val filterLambda = lambdaRecorder { _: String?, _: Int -> + Result.success(Unit) + } + val roomDirectoryList = FakeRoomDirectoryList(filterLambda = filterLambda) + val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } + val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) + presenter.test { + awaitItem().also { state -> + state.eventSink(RoomDirectoryEvents.Search("test")) + } + awaitItem().also { state -> + assertThat(state.query).isEqualTo("test") + } + advanceUntilIdle() + cancelAndIgnoreRemainingEvents() + } + assert(filterLambda) + .isCalledOnce() + .with(value("test"), any()) + } + + @Test + fun `present - emit load more event`() = runTest { + val loadMoreLambda = lambdaRecorder { -> + Result.success(Unit) + } + val roomDirectoryList = FakeRoomDirectoryList(loadMoreLambda = loadMoreLambda) + val roomDirectoryService = FakeRoomDirectoryService { roomDirectoryList } + val presenter = createRoomDirectoryPresenter(roomDirectoryService = roomDirectoryService) + presenter.test { + awaitItem().also { state -> + state.eventSink(RoomDirectoryEvents.LoadMore) + } + advanceUntilIdle() + cancelAndIgnoreRemainingEvents() + } + assert(loadMoreLambda) + .isCalledOnce() + .withNoParameter() + } + + @Test + fun `present - emit join room event`() = runTest { + val joinRoomSuccess = lambdaRecorder { roomId: RoomId -> + Result.success(roomId) + } + val joinRoomFailure = lambdaRecorder { roomId: RoomId -> + Result.failure(RuntimeException("Failed to join room $roomId")) + } + val fakeJoinRoom = FakeJoinRoom(joinRoomSuccess) + val presenter = createRoomDirectoryPresenter(joinRoom = fakeJoinRoom) + presenter.test { + awaitItem().also { state -> + state.eventSink(RoomDirectoryEvents.JoinRoom(A_ROOM_ID)) + } + awaitItem().also { state -> + assertThat(state.joinRoomAction).isEqualTo(AsyncAction.Success(A_ROOM_ID)) + fakeJoinRoom.lambda = joinRoomFailure + state.eventSink(RoomDirectoryEvents.JoinRoom(A_ROOM_ID)) + } + awaitItem().also { state -> + assertThat(state.joinRoomAction).isInstanceOf(AsyncAction.Failure::class.java) + } + } + assert(joinRoomSuccess) + .isCalledOnce() + .with(value(A_ROOM_ID)) + assert(joinRoomFailure) + .isCalledOnce() + .with(value(A_ROOM_ID)) + } + + private fun TestScope.createRoomDirectoryPresenter( + roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService( + createRoomDirectoryListFactory = { FakeRoomDirectoryList() } + ), + joinRoom: JoinRoom = FakeJoinRoom { Result.success(it) }, + ): RoomDirectoryPresenter { + return RoomDirectoryPresenter( + dispatchers = testCoroutineDispatchers(), + joinRoom = joinRoom, + roomDirectoryService = roomDirectoryService, + ) + } +} diff --git a/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt new file mode 100644 index 0000000000..bcac35fc3a --- /dev/null +++ b/features/roomdirectory/impl/src/test/kotlin/io/element/android/features/roomdirectory/impl/root/RoomDirectoryViewTest.kt @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.element.android.features.roomdirectory.impl.root + +import androidx.activity.ComponentActivity +import androidx.compose.ui.test.junit4.AndroidComposeTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.compose.ui.test.onNodeWithTag +import androidx.compose.ui.test.onNodeWithText +import androidx.compose.ui.test.performClick +import androidx.compose.ui.test.performTextInput +import androidx.test.ext.junit.runners.AndroidJUnit4 +import io.element.android.libraries.architecture.AsyncAction +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.testtags.TestTags +import io.element.android.tests.testutils.EnsureNeverCalled +import io.element.android.tests.testutils.EnsureNeverCalledWithParam +import io.element.android.tests.testutils.EventsRecorder +import io.element.android.tests.testutils.ensureCalledOnceWithParam +import org.junit.Rule +import org.junit.Test +import org.junit.rules.TestRule +import org.junit.runner.RunWith + +@RunWith(AndroidJUnit4::class) +class RoomDirectoryViewTest { + @get:Rule val rule = createAndroidComposeRule() + + @Test + fun `typing text in search field emits the expected Event`() { + val eventsRecorder = EventsRecorder() + rule.setRoomDirectoryView( + state = aRoomDirectoryState( + eventSink = eventsRecorder, + ) + ) + rule.onNodeWithTag(TestTags.searchTextField.value).performTextInput( + text = "Test" + ) + eventsRecorder.assertSingle(RoomDirectoryEvents.Search("Test")) + } + + @Test + fun `clicking on room item emits the expected Event`() { + val eventsRecorder = EventsRecorder() + val state = aRoomDirectoryState( + roomDescriptions = aRoomDescriptionList(), + eventSink = eventsRecorder, + ) + rule.setRoomDirectoryView(state = state) + val clickedRoom = state.roomDescriptions.first() + rule.onNodeWithText(clickedRoom.name).performClick() + eventsRecorder.assertSingle(RoomDirectoryEvents.JoinRoom(clickedRoom.roomId)) + } + + @Test + fun `composing load more indicator emits expected Event`() { + val eventsRecorder = EventsRecorder() + val state = aRoomDirectoryState( + displayLoadMoreIndicator = true, + eventSink = eventsRecorder, + ) + rule.setRoomDirectoryView(state = state) + eventsRecorder.assertSingle(RoomDirectoryEvents.LoadMore) + } + + @Test + fun `when joining room with success then onRoomJoined lambda is called once`() { + val eventsRecorder = EventsRecorder(expectEvents = false) + val roomDescriptions = aRoomDescriptionList() + val joinedRoomId = roomDescriptions.first().roomId + val state = aRoomDirectoryState( + joinRoomAction = AsyncAction.Success(joinedRoomId), + roomDescriptions = roomDescriptions, + eventSink = eventsRecorder, + ) + ensureCalledOnceWithParam(joinedRoomId) { callback -> + rule.setRoomDirectoryView( + state = state, + onRoomJoined = callback, + ) + } + } +} + +private fun AndroidComposeTestRule.setRoomDirectoryView( + state: RoomDirectoryState, + onBackPressed: () -> Unit = EnsureNeverCalled(), + onRoomJoined: (RoomId) -> Unit = EnsureNeverCalledWithParam(), +) { + setContent { + RoomDirectoryView( + state = state, + onRoomJoined = onRoomJoined, + onBackPressed = onBackPressed, + ) + } +} diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt index f545b27860..c860b6fc42 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListViewTest.kt @@ -191,6 +191,7 @@ private fun AndroidComposeTestRule.setRoomL onInvitesClicked: () -> Unit = EnsureNeverCalled(), onRoomSettingsClicked: (RoomId) -> Unit = EnsureNeverCalledWithParam(), onMenuActionClicked: (RoomListMenuAction) -> Unit = EnsureNeverCalledWithParam(), + onRoomDirectorySearchClicked: () -> Unit = EnsureNeverCalled(), ) { setContent { RoomListView( @@ -203,6 +204,7 @@ private fun AndroidComposeTestRule.setRoomL onInvitesClicked = onInvitesClicked, onRoomSettingsClicked = onRoomSettingsClicked, onMenuActionClicked = onMenuActionClicked, + onRoomDirectorySearchClicked = onRoomDirectorySearchClicked, ) } } diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTests.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTests.kt index d3fc434f25..b3463c549f 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTests.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/search/RoomListSearchPresenterTests.kt @@ -23,6 +23,9 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryFactory import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampFormatter import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter +import io.element.android.libraries.featureflag.api.FeatureFlagService +import io.element.android.libraries.featureflag.api.FeatureFlags +import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.roomlist.RoomListFilter import io.element.android.libraries.matrix.api.roomlist.RoomListService import io.element.android.libraries.matrix.api.roomlist.RoomSummary @@ -128,10 +131,26 @@ class RoomListSearchPresenterTests { } } } + + @Test + fun `present - room directory search`() = runTest { + val featureFlagService = FakeFeatureFlagService() + featureFlagService.setFeatureEnabled(FeatureFlags.RoomDirectorySearch, true) + val presenter = createRoomListSearchPresenter(featureFlagService = featureFlagService) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + skipItems(1) + awaitItem().let { state -> + assertThat(state.isRoomDirectorySearchEnabled).isTrue() + } + } + } } fun TestScope.createRoomListSearchPresenter( roomListService: RoomListService = FakeRoomListService(), + featureFlagService: FeatureFlagService = FakeFeatureFlagService(), ): RoomListSearchPresenter { return RoomListSearchPresenter( dataSource = RoomListSearchDataSource( @@ -141,6 +160,7 @@ fun TestScope.createRoomListSearchPresenter( roomLastMessageFormatter = FakeRoomLastMessageFormatter(), ), coroutineDispatchers = testCoroutineDispatchers(), - ) + ), + featureFlagService = featureFlagService, ) } diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index fffdfa8c7c..481c094fd4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -76,7 +76,6 @@ import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.TimeoutCancellationException import kotlinx.coroutines.cancel import kotlinx.coroutines.channels.Channel -import kotlinx.coroutines.coroutineScope import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow @@ -441,7 +440,7 @@ class RustMatrixClient( runCatching { client.removeAvatar() } } - override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { + override suspend fun joinRoom(roomId: RoomId): Result = withContext(sessionDispatcher) { runCatching { client.joinRoomById(roomId.value).destroy() try { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt index 52b8e1ba98..91b84e48c0 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDescriptionMapper.kt @@ -22,7 +22,6 @@ import org.matrix.rustcomponents.sdk.PublicRoomJoinRule import org.matrix.rustcomponents.sdk.RoomDescription as RustRoomDescription class RoomDescriptionMapper { - fun map(roomDescription: RustRoomDescription): RoomDescription { return RoomDescription( roomId = RoomId(roomDescription.roomId), diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt index f060635cdf..aff631d6b4 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RoomDirectorySearchProcessor.kt @@ -30,7 +30,6 @@ class RoomDirectorySearchProcessor( private val coroutineContext: CoroutineContext, private val roomDescriptionMapper: RoomDescriptionMapper, ) { - private val mutex = Mutex() suspend fun postUpdates(updates: List) { diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt index f2e26f51a7..9b444d5ce5 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryList.kt @@ -35,7 +35,6 @@ class RustRoomDirectoryList( coroutineScope: CoroutineScope, private val coroutineContext: CoroutineContext, ) : RoomDirectoryList { - private val hasMoreToLoad = MutableStateFlow(true) private val items = MutableSharedFlow>(replay = 1) private val processor = RoomDirectorySearchProcessor(items, coroutineContext, RoomDescriptionMapper()) diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt index 7627f90261..2939001b21 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryService.kt @@ -26,7 +26,6 @@ class RustRoomDirectoryService( private val client: Client, private val sessionDispatcher: CoroutineDispatcher, ) : RoomDirectoryService { - override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList { return RustRoomDirectoryList(client.roomDirectorySearch(), scope, sessionDispatcher) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index 1ac68a5fa5..247bc4bbe9 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -94,6 +94,9 @@ class FakeMatrixClient( private var setDisplayNameResult: Result = Result.success(Unit) private var uploadAvatarResult: Result = Result.success(Unit) private var removeAvatarResult: Result = Result.success(Unit) + var joinRoomLambda: suspend (RoomId) -> Result = { + Result.success(it) + } override suspend fun getRoom(roomId: RoomId): MatrixRoom? { return getRoomResults[roomId] @@ -181,6 +184,8 @@ class FakeMatrixClient( return removeAvatarResult } + override suspend fun joinRoom(roomId: RoomId): Result = joinRoomLambda(roomId) + override fun sessionVerificationService(): SessionVerificationService = sessionVerificationService override fun pushersService(): PushersService = pushersService diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt new file mode 100644 index 0000000000..b01501d328 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryList.kt @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.element.android.libraries.matrix.test.roomdirectory + +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.emptyFlow + +class FakeRoomDirectoryList( + override val state: Flow = emptyFlow(), + val filterLambda: (String?, Int) -> Result = { _, _ -> Result.success(Unit) }, + val loadMoreLambda: () -> Result = { Result.success(Unit) } +) : RoomDirectoryList { + override suspend fun filter(filter: String?, batchSize: Int) = filterLambda(filter, batchSize) + + override suspend fun loadMore(): Result = loadMoreLambda() +} diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt index 25f714cfde..68926b9deb 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/FakeRoomDirectoryService.kt @@ -20,8 +20,8 @@ import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService import kotlinx.coroutines.CoroutineScope -class FakeRoomDirectoryService : RoomDirectoryService { - override fun createRoomDirectoryList(scope: CoroutineScope): RoomDirectoryList { - TODO("Not yet implemented") - } +class FakeRoomDirectoryService( + private val createRoomDirectoryListFactory: (CoroutineScope) -> RoomDirectoryList = { throw AssertionError("Configure a proper factory.") } +) : RoomDirectoryService { + override fun createRoomDirectoryList(scope: CoroutineScope) = createRoomDirectoryListFactory(scope) } diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt new file mode 100644 index 0000000000..3e53a0c2e3 --- /dev/null +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/roomdirectory/RoomDescriptionFixture.kt @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.element.android.libraries.matrix.test.roomdirectory + +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.roomdirectory.RoomDescription +import io.element.android.libraries.matrix.test.A_ROOM_ID + +fun aRoomDescription( + roomId: RoomId = A_ROOM_ID, + name: String? = null, + topic: String? = null, + alias: String? = null, + avatarUrl: String? = null, + joinRule: RoomDescription.JoinRule = RoomDescription.JoinRule.UNKNOWN, + isWorldReadable: Boolean = true, + joinedMembers: Long = 2L +) = RoomDescription( + roomId = roomId, + name = name, + topic = topic, + alias = alias, + avatarUrl = avatarUrl, + joinRule = joinRule, + isWorldReadable = isWorldReadable, + joinedMembers = joinedMembers +) diff --git a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt index 1d237979e6..4374d77e52 100644 --- a/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt +++ b/libraries/testtags/src/main/kotlin/io/element/android/libraries/testtags/TestTags.kt @@ -100,4 +100,9 @@ object TestTags { * Timeline item. */ val timelineItemSenderInfo = TestTag("timeline_item-sender_info") + + /** + * Search field. + */ + val searchTextField = TestTag("search_text_field") } diff --git a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt index e651e8f968..827038785c 100644 --- a/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt +++ b/samples/minimal/src/main/kotlin/io/element/android/samples/minimal/RoomListScreen.kt @@ -118,7 +118,8 @@ class RoomListScreen( roomListService = matrixClient.roomListService, roomSummaryFactory = roomListRoomSummaryFactory, coroutineDispatchers = coroutineDispatchers, - ) + ), + featureFlagService = featureFlagService, ), sessionPreferencesStore = DefaultSessionPreferencesStore( context = context, @@ -156,6 +157,7 @@ class RoomListScreen( onInvitesClicked = {}, onRoomSettingsClicked = {}, onMenuActionClicked = {}, + onRoomDirectorySearchClicked = {}, modifier = modifier, ) diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt new file mode 100644 index 0000000000..2735827134 --- /dev/null +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/PresenterTest.kt @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2024 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * 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.element.android.tests.testutils + +import app.cash.molecule.RecompositionMode +import app.cash.molecule.moleculeFlow +import app.cash.turbine.TurbineTestContext +import app.cash.turbine.test +import io.element.android.libraries.architecture.Presenter +import kotlin.time.Duration + +suspend fun Presenter.test( + timeout: Duration? = null, + name: String? = null, + validate: suspend TurbineTestContext.() -> Unit, +) { + moleculeFlow(RecompositionMode.Immediate) { + present() + }.test(timeout, name, validate) +}