diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomDescription.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomDescription.kt index d80b3d3723..8ade9c0ec2 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomDescription.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/factories/RoomDescription.kt @@ -12,15 +12,24 @@ import io.element.android.libraries.matrix.test.A_ROOM_ID import org.matrix.rustcomponents.sdk.PublicRoomJoinRule import org.matrix.rustcomponents.sdk.RoomDescription -internal fun aRustRoomDescription(): RoomDescription { +internal fun aRustRoomDescription( + roomId: String = A_ROOM_ID.value, + name: String? = "name", + topic: String? = "topic", + alias: String? = A_ROOM_ALIAS.value, + avatarUrl: String? = "avatarUrl", + joinRule: PublicRoomJoinRule = PublicRoomJoinRule.PUBLIC, + isWorldReadable: Boolean = true, + joinedMembers: ULong = 2u, +): RoomDescription { return RoomDescription( - roomId = A_ROOM_ID.value, - name = "name", - topic = "topic", - alias = A_ROOM_ALIAS.value, - avatarUrl = "avatarUrl", - joinRule = PublicRoomJoinRule.PUBLIC, - isWorldReadable = true, - joinedMembers = 2u + roomId = roomId, + name = name, + topic = topic, + alias = alias, + avatarUrl = avatarUrl, + joinRule = joinRule, + isWorldReadable = isWorldReadable, + joinedMembers = joinedMembers, ) } diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRoomDirectorySearch.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRoomDirectorySearch.kt index 7ff65167ec..587b1bf5ce 100644 --- a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRoomDirectorySearch.kt +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/fixtures/fakes/FakeRoomDirectorySearch.kt @@ -7,7 +7,31 @@ package io.element.android.libraries.matrix.impl.fixtures.fakes +import io.element.android.tests.testutils.simulateLongTask import org.matrix.rustcomponents.sdk.NoPointer import org.matrix.rustcomponents.sdk.RoomDirectorySearch +import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntriesListener +import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate +import org.matrix.rustcomponents.sdk.TaskHandle -class FakeRoomDirectorySearch : RoomDirectorySearch(NoPointer) +class FakeRoomDirectorySearch( + var isAtLastPage: Boolean = false, +) : RoomDirectorySearch(NoPointer) { + override suspend fun isAtLastPage(): Boolean { + return isAtLastPage + } + + override suspend fun search(filter: String?, batchSize: UInt) = simulateLongTask { } + override suspend fun nextPage() = simulateLongTask { } + + private var listener: RoomDirectorySearchEntriesListener? = null + + override suspend fun results(listener: RoomDirectorySearchEntriesListener): TaskHandle { + this.listener = listener + return FakeRustTaskHandle() + } + + fun emitResult(roomEntriesUpdate: List) { + listener?.onUpdate(roomEntriesUpdate) + } +} diff --git a/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryListTest.kt b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryListTest.kt new file mode 100644 index 0000000000..71003b5b9a --- /dev/null +++ b/libraries/matrix/impl/src/test/kotlin/io/element/android/libraries/matrix/impl/roomdirectory/RustRoomDirectoryListTest.kt @@ -0,0 +1,90 @@ +/* + * Copyright 2024 New Vector Ltd. + * + * SPDX-License-Identifier: AGPL-3.0-only + * Please see LICENSE in the repository root for full details. + */ + +package io.element.android.libraries.matrix.impl.roomdirectory + +import app.cash.turbine.test +import com.google.common.truth.Truth.assertThat +import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList +import io.element.android.libraries.matrix.impl.fixtures.factories.aRustRoomDescription +import io.element.android.libraries.matrix.impl.fixtures.fakes.FakeRoomDirectorySearch +import io.element.android.libraries.matrix.test.A_ROOM_ID_2 +import io.element.android.tests.testutils.runCancellableScopeTestWithTestScope +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.StandardTestDispatcher +import kotlinx.coroutines.test.TestScope +import kotlinx.coroutines.test.runCurrent +import org.junit.Test +import org.matrix.rustcomponents.sdk.RoomDirectorySearch +import org.matrix.rustcomponents.sdk.RoomDirectorySearchEntryUpdate + +@OptIn(ExperimentalCoroutinesApi::class) +class RustRoomDirectoryListTest { + @Test + fun `check that the state emits the expected values`() = runCancellableScopeTestWithTestScope { testScope, cancellableScope -> + val fakeRoomDirectorySearch = FakeRoomDirectorySearch() + val mapper = RoomDescriptionMapper() + val sut = testScope.createRustRoomDirectoryList( + roomDirectorySearch = fakeRoomDirectorySearch, + scope = cancellableScope, + ) + // Let the mxCallback be ready + testScope.runCurrent() + sut.state.test { + sut.filter("", 20) + fakeRoomDirectorySearch.emitResult( + listOf( + RoomDirectorySearchEntryUpdate.Append(listOf(aRustRoomDescription())) + ) + ) + val initialItem = awaitItem() + assertThat(initialItem).isEqualTo( + RoomDirectoryList.State( + hasMoreToLoad = true, + items = listOf(mapper.map(aRustRoomDescription())) + ) + ) + assertThat(initialItem.hasMoreToLoad).isTrue() + fakeRoomDirectorySearch.isAtLastPage = true + sut.loadMore() + fakeRoomDirectorySearch.emitResult( + listOf( + RoomDirectorySearchEntryUpdate.Append(listOf(aRustRoomDescription(A_ROOM_ID_2.value))) + ) + ) + val nextItem = awaitItem() + assertThat(nextItem).isEqualTo( + RoomDirectoryList.State( + hasMoreToLoad = false, + items = listOf( + mapper.map(aRustRoomDescription()), + ) + ) + ) + val finalItem = awaitItem() + assertThat(finalItem).isEqualTo( + RoomDirectoryList.State( + hasMoreToLoad = false, + items = listOf( + mapper.map(aRustRoomDescription()), + mapper.map(aRustRoomDescription(A_ROOM_ID_2.value)), + ) + ) + ) + } + } + + private fun TestScope.createRustRoomDirectoryList( + roomDirectorySearch: RoomDirectorySearch = FakeRoomDirectorySearch(), + scope: CoroutineScope, + ) = RustRoomDirectoryList( + inner = roomDirectorySearch, + coroutineScope = scope, + coroutineContext = StandardTestDispatcher(testScheduler), + ) +} diff --git a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RunCancellableTest.kt b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RunCancellableTest.kt index 64c36f0a8d..c3074aaf35 100644 --- a/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RunCancellableTest.kt +++ b/tests/testutils/src/main/kotlin/io/element/android/tests/testutils/RunCancellableTest.kt @@ -10,6 +10,7 @@ package io.element.android.tests.testutils import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.cancel +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest /** @@ -20,3 +21,12 @@ fun runCancellableScopeTest(block: suspend (CoroutineScope) -> Unit) = runTest { block(scope) scope.cancel() } + +/** + * Run a test with a [CoroutineScope] that will be cancelled automatically and avoiding failing the test. + */ +fun runCancellableScopeTestWithTestScope(block: suspend (testScope: TestScope, cancellableScope: CoroutineScope) -> Unit) = runTest { + val scope = CoroutineScope(coroutineContext + SupervisorJob()) + block(this, scope) + scope.cancel() +}