First Commit

This commit is contained in:
2025-12-18 16:28:50 +07:00
commit 8c3e4f491f
9974 changed files with 396488 additions and 0 deletions
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2022-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
plugins {
id("io.element.android-library")
}
android {
namespace = "io.element.android.libraries.matrix.test"
}
dependencies {
api(projects.libraries.core)
api(projects.libraries.matrix.api)
api(libs.coroutines.core)
implementation(libs.coroutines.test)
implementation(projects.libraries.matrix.impl)
implementation(projects.services.analytics.api)
implementation(projects.tests.testutils)
implementation(libs.kotlinx.collections.immutable)
}
@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?><!--
~ Copyright (c) 2025 Element Creations Ltd.
~ Copyright 2022 New Vector Ltd.
~
~ SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
~ Please see LICENSE files in the repository root for full details.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<uses-permission android:name="android.permission.INTERNET" />
</manifest>
@@ -0,0 +1,354 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.createroom.CreateRoomParameters
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.notification.NotificationService
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.oidc.AccountManagementAction
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.NotJoinedRoom
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
import io.element.android.libraries.matrix.api.room.alias.ResolvedRoomAlias
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import io.element.android.libraries.matrix.api.roomlist.RoomListService
import io.element.android.libraries.matrix.api.spaces.SpaceService
import io.element.android.libraries.matrix.api.sync.SlidingSyncVersion
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.user.MatrixSearchUserResults
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.test.encryption.FakeEncryptionService
import io.element.android.libraries.matrix.test.media.FakeMatrixMediaLoader
import io.element.android.libraries.matrix.test.media.FakeMediaPreviewService
import io.element.android.libraries.matrix.test.notification.FakeNotificationService
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.pushers.FakePushersService
import io.element.android.libraries.matrix.test.roomdirectory.FakeRoomDirectoryService
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
import io.element.android.libraries.matrix.test.spaces.FakeSpaceService
import io.element.android.libraries.matrix.test.sync.FakeSyncService
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.TestScope
import java.util.Optional
class FakeMatrixClient(
override val sessionId: SessionId = A_SESSION_ID,
override val deviceId: DeviceId = A_DEVICE_ID,
override val sessionCoroutineScope: CoroutineScope = TestScope(),
private val userDisplayName: String? = A_USER_NAME,
private val userAvatarUrl: String? = AN_AVATAR_URL,
override val roomListService: RoomListService = FakeRoomListService(),
override val spaceService: SpaceService = FakeSpaceService(),
override val matrixMediaLoader: MatrixMediaLoader = FakeMatrixMediaLoader(),
override val sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
override val pushersService: PushersService = FakePushersService(),
override val notificationService: NotificationService = FakeNotificationService(),
override val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(),
override val syncService: SyncService = FakeSyncService(),
override val encryptionService: EncryptionService = FakeEncryptionService(),
override val roomDirectoryService: RoomDirectoryService = FakeRoomDirectoryService(),
override val mediaPreviewService: MediaPreviewService = FakeMediaPreviewService(),
override val roomMembershipObserver: RoomMembershipObserver = RoomMembershipObserver(),
private val accountManagementUrlResult: (AccountManagementAction?) -> Result<String?> = { lambdaError() },
private val resolveRoomAliasResult: (RoomAlias) -> Result<Optional<ResolvedRoomAlias>> = {
Result.success(
Optional.of(ResolvedRoomAlias(A_ROOM_ID, emptyList()))
)
},
private val getNotJoinedRoomResult: (RoomIdOrAlias, List<String>) -> Result<NotJoinedRoom> = { _, _ -> lambdaError() },
private val clearCacheLambda: () -> Unit = { lambdaError() },
private val userIdServerNameLambda: () -> String = { lambdaError() },
private val getUrlLambda: (String) -> Result<ByteArray> = { lambdaError() },
private val canDeactivateAccountResult: () -> Boolean = { lambdaError() },
private val deactivateAccountResult: (String, Boolean) -> Result<Unit> = { _, _ -> lambdaError() },
private val currentSlidingSyncVersionLambda: () -> Result<SlidingSyncVersion> = { lambdaError() },
private val ignoreUserResult: (UserId) -> Result<Unit> = { lambdaError() },
private var unIgnoreUserResult: (UserId) -> Result<Unit> = { Result.success(Unit) },
private val canReportRoomLambda: () -> Boolean = { false },
private val isLivekitRtcSupportedLambda: () -> Boolean = { false },
override val ignoredUsersFlow: StateFlow<ImmutableList<UserId>> = MutableStateFlow(persistentListOf()),
private val getMaxUploadSizeResult: () -> Result<Long> = { lambdaError() },
private val getJoinedRoomIdsResult: () -> Result<Set<RoomId>> = { Result.success(emptySet()) },
private val getRecentEmojisLambda: () -> Result<List<String>> = { Result.success(emptyList()) },
private val addRecentEmojiLambda: (String) -> Result<Unit> = { Result.success(Unit) },
private val markRoomAsFullyReadResult: (RoomId, EventId) -> Result<Unit> = { _, _ -> lambdaError() },
) : MatrixClient {
var setDisplayNameCalled: Boolean = false
private set
var uploadAvatarCalled: Boolean = false
private set
var removeAvatarCalled: Boolean = false
private set
private val _userProfile: MutableStateFlow<MatrixUser> = MutableStateFlow(MatrixUser(sessionId, userDisplayName, userAvatarUrl))
override val userProfile: StateFlow<MatrixUser> = _userProfile
private var createRoomResult: Result<RoomId> = Result.success(A_ROOM_ID)
private var createDmResult: Result<RoomId> = Result.success(A_ROOM_ID)
private var findDmResult: Result<RoomId?> = Result.success(A_ROOM_ID)
private val getRoomResults = mutableMapOf<RoomId, BaseRoom>()
private val searchUserResults = mutableMapOf<String, Result<MatrixSearchUserResults>>()
private val getProfileResults = mutableMapOf<UserId, Result<MatrixUser>>()
private var uploadMediaResult: Result<String> = Result.success(AN_AVATAR_URL)
private var setDisplayNameResult: Result<Unit> = Result.success(Unit)
private var uploadAvatarResult: Result<Unit> = Result.success(Unit)
private var removeAvatarResult: Result<Unit> = Result.success(Unit)
var joinRoomLambda: (RoomId) -> Result<RoomInfo?> = {
Result.success(null)
}
var joinRoomByIdOrAliasLambda: (RoomIdOrAlias, List<String>) -> Result<RoomInfo?> = { _, _ ->
Result.success(null)
}
var knockRoomLambda: (RoomIdOrAlias, String, List<String>) -> Result<RoomInfo?> = { _, _, _ ->
Result.success(null)
}
var getRoomInfoFlowLambda = { _: RoomId ->
flowOf<Optional<RoomInfo>>(Optional.empty())
}
var logoutLambda: (Boolean, Boolean) -> Unit = { _, _ -> }
override suspend fun getRoom(roomId: RoomId): BaseRoom? {
return getRoomResults[roomId]
}
override suspend fun getJoinedRoom(roomId: RoomId): JoinedRoom? {
return getRoomResults[roomId] as? JoinedRoom
}
override suspend fun findDM(userId: UserId): Result<RoomId?> {
return findDmResult
}
override suspend fun getJoinedRoomIds(): Result<Set<RoomId>> {
return getJoinedRoomIdsResult()
}
override suspend fun ignoreUser(userId: UserId): Result<Unit> = simulateLongTask {
return ignoreUserResult(userId)
}
override suspend fun unignoreUser(userId: UserId): Result<Unit> = simulateLongTask {
return unIgnoreUserResult(userId)
}
override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result<RoomId> = simulateLongTask {
return createRoomResult
}
override suspend fun createDM(userId: UserId): Result<RoomId> = simulateLongTask {
return createDmResult
}
override suspend fun getProfile(userId: UserId): Result<MatrixUser> {
return getProfileResults[userId] ?: Result.failure(IllegalStateException("No profile found for $userId"))
}
override suspend fun searchUsers(searchTerm: String, limit: Long): Result<MatrixSearchUserResults> {
return searchUserResults[searchTerm] ?: Result.failure(IllegalStateException("No response defined for $searchTerm"))
}
override suspend fun getCacheSize(): Long {
return 0
}
override suspend fun clearCache() = simulateLongTask {
clearCacheLambda()
}
override suspend fun logout(userInitiated: Boolean, ignoreSdkError: Boolean) = simulateLongTask {
logoutLambda(ignoreSdkError, userInitiated)
}
override fun canDeactivateAccount() = canDeactivateAccountResult()
override suspend fun deactivateAccount(password: String, eraseData: Boolean): Result<Unit> = simulateLongTask {
deactivateAccountResult(password, eraseData)
}
override suspend fun getUserProfile(): Result<MatrixUser> = simulateLongTask {
val result = getProfileResults[sessionId]?.getOrNull() ?: MatrixUser(sessionId, userDisplayName, userAvatarUrl)
_userProfile.tryEmit(result)
return Result.success(result)
}
override suspend fun getAccountManagementUrl(action: AccountManagementAction?): Result<String?> = simulateLongTask {
accountManagementUrlResult(action)
}
override suspend fun uploadMedia(
mimeType: String,
data: ByteArray,
): Result<String> {
return uploadMediaResult
}
override suspend fun setDisplayName(displayName: String): Result<Unit> = simulateLongTask {
setDisplayNameCalled = true
return setDisplayNameResult
}
override suspend fun uploadAvatar(mimeType: String, data: ByteArray): Result<Unit> = simulateLongTask {
uploadAvatarCalled = true
return uploadAvatarResult
}
override suspend fun removeAvatar(): Result<Unit> = simulateLongTask {
removeAvatarCalled = true
return removeAvatarResult
}
override suspend fun joinRoom(roomId: RoomId): Result<RoomInfo?> = joinRoomLambda(roomId)
override suspend fun joinRoomByIdOrAlias(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<RoomInfo?> {
return joinRoomByIdOrAliasLambda(roomIdOrAlias, serverNames)
}
override suspend fun knockRoom(roomIdOrAlias: RoomIdOrAlias, message: String, serverNames: List<String>): Result<RoomInfo?> {
return knockRoomLambda(roomIdOrAlias, message, serverNames)
}
// Mocks
fun givenCreateRoomResult(result: Result<RoomId>) {
createRoomResult = result
}
fun givenCreateDmResult(result: Result<RoomId>) {
createDmResult = result
}
fun givenFindDmResult(result: Result<RoomId?>) {
findDmResult = result
}
fun givenGetRoomResult(roomId: RoomId, result: BaseRoom?) {
if (result == null) {
getRoomResults.remove(roomId)
} else {
getRoomResults[roomId] = result
}
}
fun givenSearchUsersResult(searchTerm: String, result: Result<MatrixSearchUserResults>) {
searchUserResults[searchTerm] = result
}
fun givenGetProfileResult(userId: UserId, result: Result<MatrixUser>) {
getProfileResults[userId] = result
}
fun givenUploadMediaResult(result: Result<String>) {
uploadMediaResult = result
}
fun givenSetDisplayNameResult(result: Result<Unit>) {
setDisplayNameResult = result
}
fun givenUploadAvatarResult(result: Result<Unit>) {
uploadAvatarResult = result
}
fun givenRemoveAvatarResult(result: Result<Unit>) {
removeAvatarResult = result
}
private val visitedRoomsId: MutableList<RoomId> = mutableListOf()
override suspend fun trackRecentlyVisitedRoom(roomId: RoomId): Result<Unit> {
visitedRoomsId.removeAll { it == roomId }
visitedRoomsId.add(0, roomId)
return Result.success(Unit)
}
override suspend fun resolveRoomAlias(roomAlias: RoomAlias): Result<Optional<ResolvedRoomAlias>> = simulateLongTask {
resolveRoomAliasResult(roomAlias)
}
override suspend fun getRoomPreview(roomIdOrAlias: RoomIdOrAlias, serverNames: List<String>): Result<NotJoinedRoom> = simulateLongTask {
getNotJoinedRoomResult(roomIdOrAlias, serverNames)
}
override suspend fun getRecentlyVisitedRooms(): Result<List<RoomId>> {
return Result.success(visitedRoomsId)
}
override fun getRoomInfoFlow(roomId: RoomId) = getRoomInfoFlowLambda(roomId)
var setAllSendQueuesEnabledLambda = lambdaRecorder(ensureNeverCalled = true) { _: Boolean ->
// no-op
}
override suspend fun setAllSendQueuesEnabled(enabled: Boolean) = setAllSendQueuesEnabledLambda(enabled)
var sendQueueDisabledFlow = emptyFlow<RoomId>()
override fun sendQueueDisabledFlow(): Flow<RoomId> = sendQueueDisabledFlow
override fun userIdServerName(): String {
return userIdServerNameLambda()
}
override suspend fun getUrl(url: String): Result<ByteArray> {
return getUrlLambda(url)
}
override suspend fun currentSlidingSyncVersion(): Result<SlidingSyncVersion> {
return currentSlidingSyncVersionLambda()
}
override suspend fun canReportRoom(): Boolean {
return canReportRoomLambda()
}
override suspend fun isLivekitRtcSupported(): Boolean {
return isLivekitRtcSupportedLambda()
}
override suspend fun getMaxFileUploadSize(): Result<Long> {
return getMaxUploadSizeResult()
}
override suspend fun addRecentEmoji(emoji: String): Result<Unit> {
return addRecentEmojiLambda(emoji)
}
override suspend fun getRecentEmojis(): Result<List<String>> {
return getRecentEmojisLambda()
}
override suspend fun markRoomAsFullyRead(roomId: RoomId, eventId: EventId): Result<Unit> {
return markRoomAsFullyReadResult(roomId, eventId)
}
}
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.MatrixClientProvider
import io.element.android.libraries.matrix.api.core.SessionId
class FakeMatrixClientProvider(
var getClient: (SessionId) -> Result<MatrixClient> = { Result.success(FakeMatrixClient()) }
) : MatrixClientProvider {
override suspend fun getOrRestore(sessionId: SessionId): Result<MatrixClient> = getClient(sessionId)
override fun getOrNull(sessionId: SessionId): MatrixClient? = getClient(sessionId).getOrNull()
}
@@ -0,0 +1,13 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test
import io.element.android.libraries.matrix.api.SdkMetadata
class FakeSdkMetadata(override val sdkGitSha: String) : SdkMetadata
@@ -0,0 +1,102 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test
import androidx.annotation.ColorInt
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.SpaceId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UniqueId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
const val A_USER_NAME = "alice"
const val A_USER_NAME_2 = "Bob"
const val A_PASSWORD = "password"
const val A_PASSPHRASE = "passphrase"
const val A_SECRET = "secret"
const val AN_APPLICATION_NAME = "AppName"
const val AN_APPLICATION_NAME_DESKTOP = "AppNameDesktop"
val A_USER_ID = UserId("@alice:server.org")
val A_USER_ID_2 = UserId("@bob:server.org")
val A_USER_ID_3 = UserId("@carol:server.org")
val A_USER_ID_4 = UserId("@david:server.org")
val A_USER_ID_5 = UserId("@eve:server.org")
val A_USER_ID_6 = UserId("@justin:server.org")
val A_USER_ID_7 = UserId("@mallory:server.org")
val A_USER_ID_8 = UserId("@susie:server.org")
val A_USER_ID_9 = UserId("@victor:server.org")
val A_USER_ID_10 = UserId("@walter:server.org")
val A_SESSION_ID: SessionId = A_USER_ID
val A_SESSION_ID_2: SessionId = A_USER_ID_2
val A_SPACE_ID = SpaceId("!aSpaceId:domain")
val A_SPACE_ID_2 = SpaceId("!aSpaceId2:domain")
val A_ROOM_ID = RoomId("!aRoomId:domain")
val A_ROOM_ID_2 = RoomId("!aRoomId2:domain")
val A_ROOM_ID_3 = RoomId("!aRoomId3:domain")
val A_THREAD_ID = ThreadId("\$aThreadId")
val A_THREAD_ID_2 = ThreadId("\$aThreadId2")
val AN_EVENT_ID = EventId("\$anEventId")
val AN_EVENT_ID_2 = EventId("\$anEventId2")
val AN_EVENT_ID_3 = EventId("\$anEventId3")
val A_ROOM_ALIAS = RoomAlias("#alias1:domain")
val A_TRANSACTION_ID = TransactionId("aTransactionId")
val A_DEVICE_ID = DeviceId("ILAKNDNASDLK")
val A_UNIQUE_ID = UniqueId("aUniqueId")
val A_UNIQUE_ID_2 = UniqueId("aUniqueId2")
const val A_ROOM_NAME = "A room name"
const val A_ROOM_TOPIC = "A room topic"
const val A_ROOM_RAW_NAME = "A room raw name"
const val A_MESSAGE = "Hello world!"
const val A_REPLY = "OK, I'll be there!"
const val ANOTHER_MESSAGE = "Hello universe!"
const val A_CAPTION = "A media caption"
const val A_REASON = "A reason"
const val A_SPACE_NAME = "A space name"
const val A_REDACTION_REASON = "A redaction reason"
const val A_HOMESERVER_URL = "matrix.org"
const val A_HOMESERVER_URL_2 = "matrix-client.org"
const val AN_ACCOUNT_PROVIDER_URL = "https://account.provider.org"
const val AN_ACCOUNT_PROVIDER = "matrix.org"
const val AN_ACCOUNT_PROVIDER_2 = "element.io"
const val AN_ACCOUNT_PROVIDER_3 = "other.io"
val A_ROOM_NOTIFICATION_MODE = RoomNotificationMode.MUTE
const val AN_AVATAR_URL = "mxc://data"
const val A_FAILURE_REASON = "There has been a failure"
@Suppress("unused")
val A_THROWABLE = Throwable(A_FAILURE_REASON)
val AN_EXCEPTION = Exception(A_FAILURE_REASON)
const val A_RECOVERY_KEY = "1234 5678"
val A_SERVER_LIST = listOf("server1", "server2")
const val A_TIMESTAMP = 567L
const val A_FORMATTED_DATE = "April 6, 1980 at 6:35 PM"
const val A_LOGIN_HINT = "mxid:@alice:example.org"
@ColorInt
const val A_COLOR_INT: Int = 0xFFFF0000.toInt()
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.auth
import io.element.android.libraries.matrix.api.auth.HomeServerLoginCompatibilityChecker
class FakeHomeServerLoginCompatibilityChecker(
private val checkResult: (String) -> Result<Boolean>,
) : HomeServerLoginCompatibilityChecker {
override suspend fun check(url: String): Result<Boolean> {
return checkResult(url)
}
}
@@ -0,0 +1,111 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.auth
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.auth.MatrixAuthenticationService
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.api.auth.OidcDetails
import io.element.android.libraries.matrix.api.auth.OidcPrompt
import io.element.android.libraries.matrix.api.auth.external.ExternalSession
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
import io.element.android.libraries.matrix.api.auth.qrlogin.QrCodeLoginStep
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.simulateLongTask
val A_OIDC_DATA = OidcDetails(url = "a-url")
class FakeMatrixAuthenticationService(
var matrixClientResult: ((SessionId) -> Result<MatrixClient>)? = null,
var loginWithQrCodeResult: (qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit) -> Result<SessionId> =
lambdaRecorder<MatrixQrCodeLoginData, (QrCodeLoginStep) -> Unit, Result<SessionId>> { _, _ -> Result.success(A_SESSION_ID) },
private val importCreatedSessionLambda: (ExternalSession) -> Result<SessionId> = { lambdaError() },
private val setHomeserverResult: (String) -> Result<MatrixHomeServerDetails> = { lambdaError() },
) : MatrixAuthenticationService {
private var oidcError: Throwable? = null
private var oidcCancelError: Throwable? = null
private var loginError: Throwable? = null
private var matrixClient: MatrixClient? = null
private var onAuthenticationListener: ((MatrixClient) -> Unit)? = null
override suspend fun restoreSession(sessionId: SessionId): Result<MatrixClient> {
matrixClientResult?.let {
return it.invoke(sessionId)
}
return if (matrixClient != null) {
onAuthenticationListener?.invoke(matrixClient!!)
Result.success(matrixClient!!)
} else {
Result.failure(IllegalStateException())
}
}
override suspend fun setHomeserver(homeserver: String): Result<MatrixHomeServerDetails> = simulateLongTask {
setHomeserverResult(homeserver)
}
override suspend fun login(username: String, password: String): Result<SessionId> = simulateLongTask {
loginError?.let { Result.failure(it) } ?: run {
onAuthenticationListener?.invoke(matrixClient ?: FakeMatrixClient())
Result.success(A_USER_ID)
}
}
override suspend fun importCreatedSession(externalSession: ExternalSession): Result<SessionId> = simulateLongTask {
return importCreatedSessionLambda(externalSession)
}
override suspend fun getOidcUrl(
prompt: OidcPrompt,
loginHint: String?,
): Result<OidcDetails> = simulateLongTask {
oidcError?.let { Result.failure(it) } ?: Result.success(A_OIDC_DATA)
}
override suspend fun cancelOidcLogin(): Result<Unit> {
return oidcCancelError?.let { Result.failure(it) } ?: Result.success(Unit)
}
override suspend fun loginWithOidc(callbackUrl: String): Result<SessionId> = simulateLongTask {
loginError?.let { Result.failure(it) } ?: run {
onAuthenticationListener?.invoke(matrixClient ?: FakeMatrixClient())
Result.success(A_USER_ID)
}
}
override suspend fun loginWithQrCode(qrCodeData: MatrixQrCodeLoginData, progress: (QrCodeLoginStep) -> Unit): Result<SessionId> = simulateLongTask {
onAuthenticationListener?.invoke(matrixClient ?: FakeMatrixClient())
loginWithQrCodeResult(qrCodeData, progress)
}
override fun listenToNewMatrixClients(lambda: (MatrixClient) -> Unit) {
onAuthenticationListener = lambda
}
fun givenOidcError(throwable: Throwable?) {
oidcError = throwable
}
fun givenOidcCancelError(throwable: Throwable?) {
oidcCancelError = throwable
}
fun givenLoginError(throwable: Throwable?) {
loginError = throwable
}
fun givenMatrixClient(matrixClient: MatrixClient) {
this.matrixClient = matrixClient
}
}
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.auth
import io.element.android.libraries.matrix.api.auth.OidcRedirectUrlProvider
const val FAKE_REDIRECT_URL = "io.element.android:/"
class FakeOidcRedirectUrlProvider(
private val provideResult: String = FAKE_REDIRECT_URL,
) : OidcRedirectUrlProvider {
override fun provide() = provideResult
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.auth
import io.element.android.libraries.matrix.api.auth.MatrixHomeServerDetails
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
fun aMatrixHomeServerDetails(
url: String = A_HOMESERVER_URL,
supportsPasswordLogin: Boolean = false,
supportsOidcLogin: Boolean = false,
) = MatrixHomeServerDetails(
url = url,
supportsPasswordLogin = supportsPasswordLogin,
supportsOidcLogin = supportsOidcLogin,
)
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.auth.qrlogin
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginData
import io.element.android.libraries.matrix.api.auth.qrlogin.MatrixQrCodeLoginDataFactory
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.lambda.lambdaRecorder
class FakeMatrixQrCodeLoginDataFactory(
var parseQrCodeLoginDataResult: () -> Result<MatrixQrCodeLoginData> =
lambdaRecorder<Result<MatrixQrCodeLoginData>> { Result.success(FakeMatrixQrCodeLoginData()) },
) : MatrixQrCodeLoginDataFactory {
override fun parseQrCodeData(data: ByteArray): Result<MatrixQrCodeLoginData> {
return parseQrCodeLoginDataResult()
}
}
class FakeMatrixQrCodeLoginData(
private val serverNameResult: () -> String? = { lambdaError() },
) : MatrixQrCodeLoginData {
override fun serverName() = serverNameResult()
}
@@ -0,0 +1,44 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.core
import io.element.android.libraries.core.meta.BuildMeta
import io.element.android.libraries.core.meta.BuildType
fun aBuildMeta(
buildType: BuildType = BuildType.DEBUG,
isDebuggable: Boolean = true,
applicationName: String = "",
productionApplicationName: String = applicationName,
desktopApplicationName: String = applicationName,
applicationId: String = "",
isEnterpriseBuild: Boolean = false,
lowPrivacyLoggingEnabled: Boolean = true,
versionName: String = "",
versionCode: Long = 0,
gitRevision: String = "",
gitBranchName: String = "",
flavorDescription: String = "",
flavorShortDescription: String = "",
) = BuildMeta(
buildType = buildType,
isDebuggable = isDebuggable,
applicationName = applicationName,
productionApplicationName = productionApplicationName,
desktopApplicationName = desktopApplicationName,
applicationId = applicationId,
isEnterpriseBuild = isEnterpriseBuild,
lowPrivacyLoggingEnabled = lowPrivacyLoggingEnabled,
versionName = versionName,
versionCode = versionCode,
gitRevision = gitRevision,
gitBranchName = gitBranchName,
flavorDescription = flavorDescription,
flavorShortDescription = flavorShortDescription,
)
@@ -0,0 +1,20 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.core
import io.element.android.libraries.matrix.api.core.SendHandle
import io.element.android.tests.testutils.simulateLongTask
class FakeSendHandle(
var retryLambda: () -> Result<Unit> = { Result.success(Unit) }
) : SendHandle {
override suspend fun retry(): Result<Unit> = simulateLongTask {
return retryLambda()
}
}
@@ -0,0 +1,148 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.encryption
import io.element.android.libraries.architecture.AsyncData
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.BackupState
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
import io.element.android.libraries.matrix.api.encryption.EnableRecoveryProgress
import io.element.android.libraries.matrix.api.encryption.EncryptionService
import io.element.android.libraries.matrix.api.encryption.IdentityResetHandle
import io.element.android.libraries.matrix.api.encryption.RecoveryState
import io.element.android.libraries.matrix.api.encryption.identity.IdentityState
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
class FakeEncryptionService(
var startIdentityResetLambda: () -> Result<IdentityResetHandle?> = { lambdaError() },
private val pinUserIdentityResult: (UserId) -> Result<Unit> = { lambdaError() },
private val withdrawVerificationResult: (UserId) -> Result<Unit> = { lambdaError() },
private val getUserIdentityResult: (UserId) -> Result<IdentityState?> = { lambdaError() },
private val enableRecoveryLambda: (Boolean) -> Result<Unit> = { lambdaError() },
) : EncryptionService {
private var disableRecoveryFailure: Exception? = null
override val backupStateStateFlow: MutableStateFlow<BackupState> = MutableStateFlow(BackupState.UNKNOWN)
override val recoveryStateStateFlow: MutableStateFlow<RecoveryState> = MutableStateFlow(RecoveryState.UNKNOWN)
override val enableRecoveryProgressStateFlow: MutableStateFlow<EnableRecoveryProgress> = MutableStateFlow(EnableRecoveryProgress.Starting)
override val isLastDevice: MutableStateFlow<Boolean> = MutableStateFlow(false)
override val hasDevicesToVerifyAgainst: MutableStateFlow<AsyncData<Boolean>> = MutableStateFlow(AsyncData.Uninitialized)
private var waitForBackupUploadSteadyStateFlow: Flow<BackupUploadState> = flowOf()
private var recoverFailure: Exception? = null
private var doesBackupExistOnServerResult: Result<Boolean> = Result.success(true)
private var enableBackupsFailure: Exception? = null
private var curve25519: String? = null
private var ed25519: String? = null
fun givenEnableBackupsFailure(exception: Exception?) {
enableBackupsFailure = exception
}
override suspend fun enableBackups(): Result<Unit> = simulateLongTask {
enableBackupsFailure?.let { return Result.failure(it) }
return Result.success(Unit)
}
fun givenDisableRecoveryFailure(exception: Exception) {
disableRecoveryFailure = exception
}
fun givenRecoverFailure(exception: Exception?) {
recoverFailure = exception
}
override suspend fun disableRecovery(): Result<Unit> = simulateLongTask {
disableRecoveryFailure?.let { return Result.failure(it) }
return Result.success(Unit)
}
fun givenDoesBackupExistOnServerResult(result: Result<Boolean>) {
doesBackupExistOnServerResult = result
}
override suspend fun doesBackupExistOnServer(): Result<Boolean> = simulateLongTask {
return doesBackupExistOnServerResult
}
override suspend fun recover(recoveryKey: String): Result<Unit> = simulateLongTask {
recoverFailure?.let { return Result.failure(it) }
return Result.success(Unit)
}
fun emitIsLastDevice(isLastDevice: Boolean) {
this.isLastDevice.value = isLastDevice
}
fun emitHasDevicesToVerifyAgainst(hasDevicesToVerifyAgainst: AsyncData<Boolean>) {
this.hasDevicesToVerifyAgainst.value = hasDevicesToVerifyAgainst
}
override suspend fun resetRecoveryKey(): Result<String> = simulateLongTask {
return Result.success(FAKE_RECOVERY_KEY)
}
override suspend fun enableRecovery(waitForBackupsToUpload: Boolean): Result<Unit> = simulateLongTask {
return enableRecoveryLambda(waitForBackupsToUpload)
}
fun givenWaitForBackupUploadSteadyStateFlow(flow: Flow<BackupUploadState>) {
waitForBackupUploadSteadyStateFlow = flow
}
override fun waitForBackupUploadSteadyState(): Flow<BackupUploadState> {
return waitForBackupUploadSteadyStateFlow
}
fun givenDeviceKeys(curve25519: String?, ed25519: String?) {
this.curve25519 = curve25519
this.ed25519 = ed25519
}
override suspend fun deviceCurve25519(): String? = curve25519
override suspend fun deviceEd25519(): String? = ed25519
suspend fun emitBackupState(state: BackupState) {
backupStateStateFlow.emit(state)
}
suspend fun emitRecoveryState(state: RecoveryState) {
recoveryStateStateFlow.emit(state)
}
suspend fun emitEnableRecoveryProgress(state: EnableRecoveryProgress) {
enableRecoveryProgressStateFlow.emit(state)
}
override suspend fun startIdentityReset(): Result<IdentityResetHandle?> {
return startIdentityResetLambda()
}
override suspend fun pinUserIdentity(userId: UserId): Result<Unit> {
return pinUserIdentityResult(userId)
}
override suspend fun withdrawVerification(userId: UserId): Result<Unit> {
return withdrawVerificationResult(userId)
}
override suspend fun getUserIdentity(userId: UserId, fallbackToServer: Boolean): Result<IdentityState?> = simulateLongTask {
return getUserIdentityResult(userId)
}
companion object {
const val FAKE_RECOVERY_KEY = "fake"
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.encryption
import io.element.android.libraries.matrix.api.encryption.IdentityOidcResetHandle
import io.element.android.libraries.matrix.api.encryption.IdentityPasswordResetHandle
class FakeIdentityOidcResetHandle(
override val url: String = "",
var resetOidcLambda: () -> Result<Unit> = { error("Not implemented") },
var cancelLambda: () -> Unit = { error("Not implemented") },
) : IdentityOidcResetHandle {
override suspend fun resetOidc(): Result<Unit> {
return resetOidcLambda()
}
override suspend fun cancel() {
cancelLambda()
}
}
class FakeIdentityPasswordResetHandle(
var resetPasswordLambda: (String) -> Result<Unit> = { _ -> error("Not implemented") },
var cancelLambda: () -> Unit = { error("Not implemented") },
) : IdentityPasswordResetHandle {
override suspend fun resetPassword(password: String): Result<Unit> {
return resetPasswordLambda(password)
}
override suspend fun cancel() {
cancelLambda()
}
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.media
import io.element.android.libraries.matrix.api.media.MatrixMediaLoader
import io.element.android.libraries.matrix.api.media.MediaFile
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.tests.testutils.simulateLongTask
class FakeMatrixMediaLoader : MatrixMediaLoader {
var shouldFail = false
var path: String = ""
override suspend fun loadMediaContent(source: MediaSource): Result<ByteArray> = simulateLongTask {
if (shouldFail) {
Result.failure(RuntimeException())
} else {
Result.success(ByteArray(0))
}
}
override suspend fun loadMediaThumbnail(source: MediaSource, width: Long, height: Long): Result<ByteArray> = simulateLongTask {
if (shouldFail) {
Result.failure(RuntimeException())
} else {
Result.success(ByteArray(0))
}
}
override suspend fun downloadMediaFile(
source: MediaSource,
mimeType: String?,
filename: String?,
useCache: Boolean,
): Result<MediaFile> = simulateLongTask {
if (shouldFail) {
Result.failure(RuntimeException())
} else {
Result.success(FakeMediaFile(path))
}
}
}
@@ -0,0 +1,24 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.media
import io.element.android.libraries.matrix.api.media.MediaFile
import java.io.File
class FakeMediaFile(private val path: String) : MediaFile {
override fun path(): String {
return path
}
override fun persist(path: String): Boolean {
return File(path()).renameTo(File(path))
}
override fun close() = Unit
}
@@ -0,0 +1,36 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.media
import io.element.android.libraries.matrix.api.media.MediaPreviewConfig
import io.element.android.libraries.matrix.api.media.MediaPreviewService
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class FakeMediaPreviewService(
override val mediaPreviewConfigFlow: StateFlow<MediaPreviewConfig> = MutableStateFlow(MediaPreviewConfig.DEFAULT),
private val fetchMediaPreviewConfigResult: () -> Result<MediaPreviewConfig?> = { lambdaError() },
private val setMediaPreviewValueResult: (MediaPreviewValue) -> Result<Unit> = { lambdaError() },
private val setHideInviteAvatarsResult: (Boolean) -> Result<Unit> = { lambdaError() },
) : MediaPreviewService {
override suspend fun fetchMediaPreviewConfig(): Result<MediaPreviewConfig?> = simulateLongTask {
fetchMediaPreviewConfigResult()
}
override suspend fun setMediaPreviewValue(mediaPreviewValue: MediaPreviewValue): Result<Unit> = simulateLongTask {
setMediaPreviewValueResult(mediaPreviewValue)
}
override suspend fun setHideInviteAvatars(hide: Boolean): Result<Unit> = simulateLongTask {
setHideInviteAvatarsResult(hide)
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.media
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.tests.testutils.simulateLongTask
import kotlin.coroutines.cancellation.CancellationException
class FakeMediaUploadHandler(
private var result: Result<Unit> = Result.success(Unit),
) : MediaUploadHandler {
override suspend fun await(): Result<Unit> = simulateLongTask { result }
override fun cancel() {
result = Result.failure(CancellationException())
}
}
@@ -0,0 +1,16 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.media
import io.element.android.libraries.matrix.api.media.MediaSource
fun aMediaSource(url: String = "") = MediaSource(
url = url,
json = null
)
@@ -0,0 +1,15 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.mxc
import io.element.android.libraries.matrix.api.mxc.MxcTools
import io.element.android.libraries.matrix.impl.mxc.DefaultMxcTools
class FakeMxcTools(
private val delegate: MxcTools = DefaultMxcTools()
) : MxcTools by delegate
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.notification
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.api.notification.NotificationService
class FakeNotificationService : NotificationService {
private var getNotificationsResult: Result<Map<EventId, Result<NotificationData>>> = Result.success(emptyMap())
fun givenGetNotificationsResult(result: Result<Map<EventId, Result<NotificationData>>>) {
getNotificationsResult = result
}
override suspend fun getNotifications(ids: Map<RoomId, List<EventId>>): Result<Map<EventId, Result<NotificationData>>> {
return getNotificationsResult
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.notification
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.notification.NotificationContent
import io.element.android.libraries.matrix.api.notification.NotificationData
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_TIMESTAMP
import io.element.android.libraries.matrix.test.A_USER_NAME_2
fun aNotificationData(
content: NotificationContent = NotificationContent.MessageLike.RoomEncrypted,
isDirect: Boolean = false,
hasMention: Boolean = false,
threadId: ThreadId? = null,
timestamp: Long = A_TIMESTAMP,
senderDisplayName: String? = A_USER_NAME_2,
senderIsNameAmbiguous: Boolean = false,
roomDisplayName: String? = A_ROOM_NAME
): NotificationData {
return NotificationData(
sessionId = A_SESSION_ID,
eventId = AN_EVENT_ID,
threadId = threadId,
roomId = A_ROOM_ID,
senderAvatarUrl = null,
senderDisplayName = senderDisplayName,
senderIsNameAmbiguous = senderIsNameAmbiguous,
roomAvatarUrl = null,
roomDisplayName = roomDisplayName,
isDirect = isDirect,
isDm = false,
isEncrypted = false,
isNoisy = false,
timestamp = timestamp,
content = content,
hasMention = hasMention,
)
}
@@ -0,0 +1,188 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.notificationsettings
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.RoomNotificationSettings
import io.element.android.libraries.matrix.test.A_ROOM_NOTIFICATION_MODE
import io.element.android.tests.testutils.lambda.lambdaError
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
class FakeNotificationSettingsService(
initialRoomMode: RoomNotificationMode = A_ROOM_NOTIFICATION_MODE,
initialRoomModeIsDefault: Boolean = true,
initialGroupDefaultMode: RoomNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
initialEncryptedGroupDefaultMode: RoomNotificationMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY,
initialOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
initialEncryptedOneToOneDefaultMode: RoomNotificationMode = RoomNotificationMode.ALL_MESSAGES,
private val getRawPushRulesResult: () -> Result<String> = { lambdaError() },
private val getRoomsWithUserDefinedRulesResult: () -> Result<List<RoomId>> = { lambdaError() },
) : NotificationSettingsService {
private val notificationSettingsStateFlow = MutableStateFlow(Unit)
private var defaultGroupRoomNotificationMode: RoomNotificationMode = initialGroupDefaultMode
private var defaultEncryptedGroupRoomNotificationMode: RoomNotificationMode = initialEncryptedGroupDefaultMode
private var defaultOneToOneRoomNotificationMode: RoomNotificationMode = initialOneToOneDefaultMode
private var defaultEncryptedOneToOneRoomNotificationMode: RoomNotificationMode = initialEncryptedOneToOneDefaultMode
private var roomNotificationMode: RoomNotificationMode = initialRoomMode
private var roomNotificationModeIsDefault: Boolean = initialRoomModeIsDefault
private var callNotificationsEnabled = false
private var inviteNotificationsEnabled = false
private var atRoomNotificationsEnabled = false
private var setNotificationModeError: Throwable? = null
private var restoreDefaultNotificationModeError: Throwable? = null
private var setDefaultNotificationModeError: Throwable? = null
private var setAtRoomError: Throwable? = null
private var canHomeServerPushEncryptedEventsToDeviceResult = Result.success(true)
override val notificationSettingsChangeFlow: SharedFlow<Unit>
get() = notificationSettingsStateFlow
override suspend fun getRoomNotificationSettings(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationSettings> {
return Result.success(
RoomNotificationSettings(
mode = if (roomNotificationModeIsDefault) defaultEncryptedGroupRoomNotificationMode else roomNotificationMode,
isDefault = roomNotificationModeIsDefault
)
)
}
override suspend fun getDefaultRoomNotificationMode(isEncrypted: Boolean, isOneToOne: Boolean): Result<RoomNotificationMode> {
return if (isOneToOne) {
if (isEncrypted) {
Result.success(defaultEncryptedOneToOneRoomNotificationMode)
} else {
Result.success(defaultOneToOneRoomNotificationMode)
}
} else {
if (isEncrypted) {
Result.success(defaultEncryptedGroupRoomNotificationMode)
} else {
Result.success(defaultGroupRoomNotificationMode)
}
}
}
override suspend fun setDefaultRoomNotificationMode(isEncrypted: Boolean, mode: RoomNotificationMode, isOneToOne: Boolean): Result<Unit> {
val error = setDefaultNotificationModeError
if (error != null) {
return Result.failure(error)
}
if (isOneToOne) {
if (isEncrypted) {
defaultEncryptedOneToOneRoomNotificationMode = mode
} else {
defaultOneToOneRoomNotificationMode = mode
}
} else {
if (isEncrypted) {
defaultEncryptedGroupRoomNotificationMode = mode
} else {
defaultGroupRoomNotificationMode = mode
}
}
notificationSettingsStateFlow.emit(Unit)
return Result.success(Unit)
}
override suspend fun setRoomNotificationMode(roomId: RoomId, mode: RoomNotificationMode): Result<Unit> {
val error = setNotificationModeError
return if (error != null) {
Result.failure(error)
} else {
roomNotificationModeIsDefault = false
roomNotificationMode = mode
notificationSettingsStateFlow.emit(Unit)
Result.success(Unit)
}
}
override suspend fun restoreDefaultRoomNotificationMode(roomId: RoomId): Result<Unit> {
val error = restoreDefaultNotificationModeError
if (error != null) {
return Result.failure(error)
}
roomNotificationModeIsDefault = true
roomNotificationMode = defaultEncryptedGroupRoomNotificationMode
notificationSettingsStateFlow.emit(Unit)
return Result.success(Unit)
}
override suspend fun muteRoom(roomId: RoomId): Result<Unit> {
return setRoomNotificationMode(roomId, RoomNotificationMode.MUTE)
}
override suspend fun unmuteRoom(roomId: RoomId, isEncrypted: Boolean, isOneToOne: Boolean): Result<Unit> {
return restoreDefaultRoomNotificationMode(roomId)
}
override suspend fun isRoomMentionEnabled(): Result<Boolean> {
return Result.success(atRoomNotificationsEnabled)
}
override suspend fun setRoomMentionEnabled(enabled: Boolean): Result<Unit> {
val error = setAtRoomError
if (error != null) {
return Result.failure(error)
}
atRoomNotificationsEnabled = enabled
return Result.success(Unit)
}
override suspend fun isCallEnabled(): Result<Boolean> {
return Result.success(callNotificationsEnabled)
}
override suspend fun setCallEnabled(enabled: Boolean): Result<Unit> {
callNotificationsEnabled = enabled
return Result.success(Unit)
}
override suspend fun isInviteForMeEnabled(): Result<Boolean> {
return Result.success(inviteNotificationsEnabled)
}
override suspend fun setInviteForMeEnabled(enabled: Boolean): Result<Unit> {
inviteNotificationsEnabled = enabled
return Result.success(Unit)
}
override suspend fun getRoomsWithUserDefinedRules(): Result<List<RoomId>> {
return getRoomsWithUserDefinedRulesResult()
}
override suspend fun canHomeServerPushEncryptedEventsToDevice(): Result<Boolean> {
return canHomeServerPushEncryptedEventsToDeviceResult
}
fun givenSetNotificationModeError(throwable: Throwable?) {
setNotificationModeError = throwable
}
fun givenRestoreDefaultNotificationModeError(throwable: Throwable?) {
restoreDefaultNotificationModeError = throwable
}
fun givenSetAtRoomError(throwable: Throwable?) {
setAtRoomError = throwable
}
fun givenSetDefaultNotificationModeError(throwable: Throwable?) {
setDefaultNotificationModeError = throwable
}
fun givenCanHomeServerPushEncryptedEventsToDeviceResult(result: Result<Boolean>) {
canHomeServerPushEncryptedEventsToDeviceResult = result
}
override suspend fun getRawPushRules(): Result<String?> {
return getRawPushRulesResult()
}
}
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.permalink
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.permalink.PermalinkBuilder
import io.element.android.tests.testutils.lambda.lambdaError
class FakePermalinkBuilder(
private val permalinkForUserLambda: (UserId) -> Result<String> = { lambdaError() },
private val permalinkForRoomAliasLambda: (RoomAlias) -> Result<String> = { lambdaError() },
) : PermalinkBuilder {
override fun permalinkForUser(userId: UserId): Result<String> {
return permalinkForUserLambda(userId)
}
override fun permalinkForRoomAlias(roomAlias: RoomAlias): Result<String> {
return permalinkForRoomAliasLambda(roomAlias)
}
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.permalink
import io.element.android.libraries.matrix.api.permalink.PermalinkData
import io.element.android.libraries.matrix.api.permalink.PermalinkParser
import io.element.android.tests.testutils.lambda.lambdaError
class FakePermalinkParser(
private var result: (String) -> PermalinkData = { lambdaError() }
) : PermalinkParser {
fun givenResult(result: PermalinkData) {
this.result = { result }
}
override fun parse(uriString: String): PermalinkData {
return result(uriString)
}
}
@@ -0,0 +1,22 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.pushers
import io.element.android.libraries.matrix.api.pusher.PushersService
import io.element.android.libraries.matrix.api.pusher.SetHttpPusherData
import io.element.android.libraries.matrix.api.pusher.UnsetHttpPusherData
import io.element.android.tests.testutils.lambda.lambdaError
class FakePushersService(
private val setHttpPusherResult: (SetHttpPusherData) -> Result<Unit> = { lambdaError() },
private val unsetHttpPusherResult: (UnsetHttpPusherData) -> Result<Unit> = { lambdaError() },
) : PushersService {
override suspend fun setHttpPusher(setHttpPusherData: SetHttpPusherData) = setHttpPusherResult(setHttpPusherData)
override suspend fun unsetHttpPusher(unsetHttpPusherData: UnsetHttpPusherData): Result<Unit> = unsetHttpPusherResult(unsetHttpPusherData)
}
@@ -0,0 +1,265 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.SessionId
import io.element.android.libraries.matrix.api.core.ThreadId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.MessageEventType
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembersState
import io.element.android.libraries.matrix.api.room.StateEventType
import io.element.android.libraries.matrix.api.room.draft.ComposerDraft
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import io.element.android.libraries.matrix.api.room.tombstone.PredecessorRoom
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.TestScope
class FakeBaseRoom(
override val sessionId: SessionId = A_SESSION_ID,
override val roomId: RoomId = A_ROOM_ID,
initialRoomInfo: RoomInfo = aRoomInfo(),
override val roomCoroutineScope: CoroutineScope = TestScope(),
private var roomPermalinkResult: () -> Result<String> = { lambdaError() },
private var eventPermalinkResult: (EventId) -> Result<String> = { lambdaError() },
private val userDisplayNameResult: (UserId) -> Result<String?> = { lambdaError() },
private val userAvatarUrlResult: () -> Result<String?> = { lambdaError() },
private val userRoleResult: () -> Result<RoomMember.Role> = { lambdaError() },
private val getUpdatedMemberResult: (UserId) -> Result<RoomMember> = { lambdaError() },
private val joinRoomResult: () -> Result<Unit> = { lambdaError() },
private val canInviteResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canKickResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canBanResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canRedactOwnResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canRedactOtherResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canSendStateResult: (UserId, StateEventType) -> Result<Boolean> = { _, _ -> lambdaError() },
private val canUserSendMessageResult: (UserId, MessageEventType) -> Result<Boolean> = { _, _ -> lambdaError() },
private val canUserTriggerRoomNotificationResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canUserJoinCallResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val canUserPinUnpinResult: (UserId) -> Result<Boolean> = { lambdaError() },
private val setIsFavoriteResult: (Boolean) -> Result<Unit> = { lambdaError() },
private val markAsReadResult: (ReceiptType) -> Result<Unit> = { Result.success(Unit) },
private val powerLevelsResult: () -> Result<RoomPowerLevelsValues> = { lambdaError() },
private val leaveRoomLambda: () -> Result<Unit> = { lambdaError() },
private var updateMembersResult: () -> Unit = { lambdaError() },
private val getMembersResult: (Int) -> Result<List<RoomMember>> = { lambdaError() },
private val saveComposerDraftLambda: (ComposerDraft) -> Result<Unit> = { _: ComposerDraft -> Result.success(Unit) },
private val loadComposerDraftLambda: () -> Result<ComposerDraft?> = { Result.success<ComposerDraft?>(null) },
private val clearComposerDraftLambda: () -> Result<Unit> = { Result.success(Unit) },
private val subscribeToSyncLambda: () -> Unit = { lambdaError() },
private val getRoomVisibilityResult: () -> Result<RoomVisibility> = { lambdaError() },
private val forgetResult: () -> Result<Unit> = { lambdaError() },
private val reportRoomResult: (String?) -> Result<Unit> = { lambdaError() },
private val predecessorRoomResult: () -> PredecessorRoom? = { null },
private val threadRootIdForEventResult: (EventId) -> Result<ThreadId?> = { lambdaError() },
) : BaseRoom {
private val _roomInfoFlow: MutableStateFlow<RoomInfo> = MutableStateFlow(initialRoomInfo)
override val roomInfoFlow: StateFlow<RoomInfo> = _roomInfoFlow
fun givenRoomInfo(roomInfo: RoomInfo) {
_roomInfoFlow.tryEmit(roomInfo)
}
private val declineCallFlowMap: MutableMap<EventId, MutableSharedFlow<UserId>> = mutableMapOf()
suspend fun givenDecliner(userId: UserId, forNotificationEventId: EventId) {
declineCallFlowMap[forNotificationEventId]?.emit(userId)
}
override val membersStateFlow: MutableStateFlow<RoomMembersState> = MutableStateFlow(RoomMembersState.Unknown)
override suspend fun updateMembers() = updateMembersResult()
override suspend fun getUpdatedMember(userId: UserId): Result<RoomMember> {
return getUpdatedMemberResult(userId)
}
override suspend fun getMembers(limit: Int): Result<List<RoomMember>> {
return getMembersResult(limit)
}
override suspend fun subscribeToSync() {
subscribeToSyncLambda()
}
override suspend fun powerLevels(): Result<RoomPowerLevelsValues> {
return powerLevelsResult()
}
private var isDestroyed = false
override fun destroy() {
isDestroyed = true
}
fun assertDestroyed() {
check(isDestroyed) { "Room should be destroyed" }
}
override suspend fun userDisplayName(userId: UserId): Result<String?> = simulateLongTask {
userDisplayNameResult(userId)
}
override suspend fun userAvatarUrl(userId: UserId): Result<String?> = simulateLongTask {
userAvatarUrlResult()
}
override suspend fun userRole(userId: UserId): Result<RoomMember.Role> {
return userRoleResult()
}
override suspend fun getPermalink(): Result<String> {
return roomPermalinkResult()
}
override suspend fun getPermalinkFor(eventId: EventId): Result<String> {
return eventPermalinkResult(eventId)
}
override suspend fun getRoomVisibility(): Result<RoomVisibility> = simulateLongTask {
getRoomVisibilityResult()
}
override suspend fun leave(): Result<Unit> = simulateLongTask {
return leaveRoomLambda()
}
override suspend fun join(): Result<Unit> {
return joinRoomResult()
}
override suspend fun forget(): Result<Unit> {
return forgetResult()
}
override suspend fun canUserBan(userId: UserId): Result<Boolean> {
return canBanResult(userId)
}
override suspend fun canUserKick(userId: UserId): Result<Boolean> {
return canKickResult(userId)
}
override suspend fun canUserInvite(userId: UserId): Result<Boolean> {
return canInviteResult(userId)
}
override suspend fun canUserRedactOwn(userId: UserId): Result<Boolean> {
return canRedactOwnResult(userId)
}
override suspend fun canUserRedactOther(userId: UserId): Result<Boolean> {
return canRedactOtherResult(userId)
}
override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result<Boolean> {
return canSendStateResult(userId, type)
}
override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result<Boolean> {
return canUserSendMessageResult(userId, type)
}
override suspend fun canUserTriggerRoomNotification(userId: UserId): Result<Boolean> {
return canUserTriggerRoomNotificationResult(userId)
}
override suspend fun canUserJoinCall(userId: UserId): Result<Boolean> {
return canUserJoinCallResult(userId)
}
override suspend fun canUserPinUnpin(userId: UserId): Result<Boolean> {
return canUserPinUnpinResult(userId)
}
override suspend fun setIsFavorite(isFavorite: Boolean): Result<Unit> {
return setIsFavoriteResult(isFavorite)
}
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> {
return markAsReadResult(receiptType)
}
var setUnreadFlagCalls = mutableListOf<Boolean>()
private set
override suspend fun setUnreadFlag(isUnread: Boolean): Result<Unit> {
setUnreadFlagCalls.add(isUnread)
return Result.success(Unit)
}
override suspend fun saveComposerDraft(
composerDraft: ComposerDraft,
threadRoot: ThreadId?
) = saveComposerDraftLambda(composerDraft)
override suspend fun loadComposerDraft(threadRoot: ThreadId?) = loadComposerDraftLambda()
override suspend fun clearComposerDraft(threadRoot: ThreadId?) = clearComposerDraftLambda()
override suspend fun getUpdatedIsEncrypted(): Result<Boolean> = simulateLongTask {
Result.success(info().isEncrypted.orFalse())
}
fun givenRoomMembersState(state: RoomMembersState) {
membersStateFlow.value = state
}
override suspend fun clearEventCacheStorage(): Result<Unit> {
return Result.success(Unit)
}
override suspend fun reportRoom(reason: String?) = reportRoomResult(reason)
override suspend fun declineCall(notificationEventId: EventId): Result<Unit> {
return Result.success(Unit)
}
override suspend fun subscribeToCallDecline(notificationEventId: EventId): Flow<UserId> {
val flow = declineCallFlowMap.getOrPut(notificationEventId, { MutableSharedFlow() })
return flow
}
override fun predecessorRoom(): PredecessorRoom? = predecessorRoomResult()
fun givenUpdateMembersResult(result: () -> Unit) {
updateMembersResult = result
}
override suspend fun threadRootIdForEvent(eventId: EventId): Result<ThreadId?> {
return threadRootIdForEventResult(eventId)
}
}
fun defaultRoomPowerLevelValues() = RoomPowerLevelsValues(
ban = 50,
invite = 0,
kick = 50,
sendEvents = 0,
redactEvents = 50,
roomName = 100,
roomAvatar = 100,
roomTopic = 100,
spaceChild = 100,
)
@@ -0,0 +1,232 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.core.bool.orFalse
import io.element.android.libraries.matrix.api.core.DeviceId
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.ProgressCallback
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.SendHandle
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.encryption.identity.IdentityStateChange
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.CreateTimelineParams
import io.element.android.libraries.matrix.api.room.IntentionalMention
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMembersState
import io.element.android.libraries.matrix.api.room.RoomNotificationSettingsState
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevelsValues
import io.element.android.libraries.matrix.api.room.powerlevels.UserRoleChange
import io.element.android.libraries.matrix.api.roomdirectory.RoomVisibility
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.test.TestScope
class FakeJoinedRoom(
val baseRoom: FakeBaseRoom = FakeBaseRoom(),
override val liveTimeline: Timeline = FakeTimeline(),
override val roomCoroutineScope: CoroutineScope = TestScope(),
override val syncUpdateFlow: StateFlow<Long> = MutableStateFlow(0),
override val roomTypingMembersFlow: Flow<List<UserId>> = MutableStateFlow(emptyList()),
override val identityStateChangesFlow: Flow<List<IdentityStateChange>> = MutableStateFlow(emptyList()),
override val roomNotificationSettingsStateFlow: StateFlow<RoomNotificationSettingsState> =
MutableStateFlow(RoomNotificationSettingsState.Unknown),
override val knockRequestsFlow: Flow<List<KnockRequest>> = MutableStateFlow(emptyList()),
private val roomNotificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
private var createTimelineResult: (CreateTimelineParams) -> Result<Timeline> = { lambdaError() },
private val editMessageLambda: (EventId, String, String?, List<IntentionalMention>) -> Result<Unit> = { _, _, _, _ -> lambdaError() },
private val progressCallbackValues: List<Pair<Long, Long>> = emptyList(),
private val generateWidgetWebViewUrlResult: (MatrixWidgetSettings, String, String?, String?) -> Result<String> = { _, _, _, _ -> lambdaError() },
private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result<MatrixWidgetDriver> = { lambdaError() },
private val typingNoticeResult: (Boolean) -> Result<Unit> = { lambdaError() },
private val inviteUserResult: (UserId) -> Result<Unit> = { lambdaError() },
private val setNameResult: (String) -> Result<Unit> = { lambdaError() },
private val setTopicResult: (String) -> Result<Unit> = { lambdaError() },
private val updateAvatarResult: (String, ByteArray) -> Result<Unit> = { _, _ -> lambdaError() },
private val removeAvatarResult: () -> Result<Unit> = { lambdaError() },
private val updateUserRoleResult: (List<UserRoleChange>) -> Result<Unit> = { lambdaError() },
private val updatePowerLevelsResult: (RoomPowerLevelsValues) -> Result<Unit> = { lambdaError() },
private val resetPowerLevelsResult: () -> Result<Unit> = { lambdaError() },
private val reportContentResult: (EventId, String, UserId?) -> Result<Unit> = { _, _, _ -> lambdaError() },
private val kickUserResult: (UserId, String?) -> Result<Unit> = { _, _ -> lambdaError() },
private val banUserResult: (UserId, String?) -> Result<Unit> = { _, _ -> lambdaError() },
private val unBanUserResult: (UserId, String?) -> Result<Unit> = { _, _ -> lambdaError() },
private val ignoreDeviceTrustAndResendResult: (Map<UserId, List<DeviceId>>, SendHandle) -> Result<Unit> = { _, _ -> lambdaError() },
private val withdrawVerificationAndResendResult: (List<UserId>, SendHandle) -> Result<Unit> = { _, _ -> lambdaError() },
private val updateCanonicalAliasResult: (RoomAlias?, List<RoomAlias>) -> Result<Unit> = { _, _ -> lambdaError() },
private val updateRoomVisibilityResult: (RoomVisibility) -> Result<Unit> = { lambdaError() },
private val updateRoomHistoryVisibilityResult: (RoomHistoryVisibility) -> Result<Unit> = { lambdaError() },
private val publishRoomAliasInRoomDirectoryResult: (RoomAlias) -> Result<Boolean> = { lambdaError() },
private val removeRoomAliasFromRoomDirectoryResult: (RoomAlias) -> Result<Boolean> = { lambdaError() },
private val enableEncryptionResult: () -> Result<Unit> = { lambdaError() },
private val updateJoinRuleResult: (JoinRule) -> Result<Unit> = { lambdaError() },
private val setSendQueueEnabledResult: (Boolean) -> Unit = { _: Boolean -> },
) : JoinedRoom, BaseRoom by baseRoom {
fun givenRoomMembersState(state: RoomMembersState) {
baseRoom.givenRoomMembersState(state)
}
fun givenRoomInfo(roomInfo: RoomInfo) {
baseRoom.givenRoomInfo(roomInfo)
}
override suspend fun createTimeline(createTimelineParams: CreateTimelineParams): Result<Timeline> = simulateLongTask {
createTimelineResult(createTimelineParams)
}
override suspend fun editMessage(
eventId: EventId,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>
): Result<Unit> = simulateLongTask {
editMessageLambda(eventId, body, htmlBody, intentionalMentions)
}
override suspend fun typingNotice(isTyping: Boolean): Result<Unit> = simulateLongTask {
typingNoticeResult(isTyping)
}
override suspend fun inviteUserById(id: UserId): Result<Unit> = simulateLongTask {
inviteUserResult(id)
}
override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result<Unit> = simulateLongTask {
simulateSendMediaProgress(null)
updateAvatarResult(mimeType, data)
}
override suspend fun removeAvatar(): Result<Unit> = simulateLongTask {
removeAvatarResult()
}
override suspend fun updateRoomNotificationSettings(): Result<Unit> = simulateLongTask {
val notificationSettings = roomNotificationSettingsService.getRoomNotificationSettings(roomId, info().isEncrypted.orFalse(), isOneToOne).getOrThrow()
(roomNotificationSettingsStateFlow as MutableStateFlow).value = RoomNotificationSettingsState.Ready(notificationSettings)
return Result.success(Unit)
}
override suspend fun updateCanonicalAlias(canonicalAlias: RoomAlias?, alternativeAliases: List<RoomAlias>): Result<Unit> = simulateLongTask {
updateCanonicalAliasResult(canonicalAlias, alternativeAliases)
}
override suspend fun updateRoomVisibility(roomVisibility: RoomVisibility): Result<Unit> = simulateLongTask {
updateRoomVisibilityResult(roomVisibility)
}
override suspend fun updateHistoryVisibility(historyVisibility: RoomHistoryVisibility): Result<Unit> = simulateLongTask {
updateRoomHistoryVisibilityResult(historyVisibility)
}
override suspend fun publishRoomAliasInRoomDirectory(roomAlias: RoomAlias): Result<Boolean> = simulateLongTask {
publishRoomAliasInRoomDirectoryResult(roomAlias)
}
override suspend fun removeRoomAliasFromRoomDirectory(roomAlias: RoomAlias): Result<Boolean> = simulateLongTask {
removeRoomAliasFromRoomDirectoryResult(roomAlias)
}
override suspend fun enableEncryption(): Result<Unit> = simulateLongTask {
enableEncryptionResult().onSuccess {
baseRoom.givenRoomInfo(info().copy(isEncrypted = true))
emitSyncUpdate()
}
}
override suspend fun updateJoinRule(joinRule: JoinRule): Result<Unit> = simulateLongTask {
updateJoinRuleResult(joinRule)
}
override suspend fun updateUsersRoles(changes: List<UserRoleChange>): Result<Unit> = simulateLongTask {
updateUserRoleResult(changes)
}
override suspend fun updatePowerLevels(roomPowerLevelsValues: RoomPowerLevelsValues): Result<Unit> = simulateLongTask {
updatePowerLevelsResult(roomPowerLevelsValues)
}
override suspend fun resetPowerLevels(): Result<Unit> = simulateLongTask {
resetPowerLevelsResult()
}
override suspend fun setName(name: String): Result<Unit> = simulateLongTask {
setNameResult(name)
}
override suspend fun setTopic(topic: String): Result<Unit> = simulateLongTask {
setTopicResult(topic)
}
override suspend fun reportContent(eventId: EventId, reason: String, blockUserId: UserId?): Result<Unit> = simulateLongTask {
reportContentResult(eventId, reason, blockUserId)
}
override suspend fun kickUser(userId: UserId, reason: String?): Result<Unit> = simulateLongTask {
kickUserResult(userId, reason)
}
override suspend fun banUser(userId: UserId, reason: String?): Result<Unit> = simulateLongTask {
banUserResult(userId, reason)
}
override suspend fun unbanUser(userId: UserId, reason: String?): Result<Unit> = simulateLongTask {
unBanUserResult(userId, reason)
}
override suspend fun generateWidgetWebViewUrl(
widgetSettings: MatrixWidgetSettings,
clientId: String,
languageTag: String?,
theme: String?
): Result<String> = simulateLongTask {
generateWidgetWebViewUrlResult(widgetSettings, clientId, languageTag, theme)
}
override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result<MatrixWidgetDriver> {
return getWidgetDriverResult(widgetSettings)
}
override suspend fun setSendQueueEnabled(enabled: Boolean) = simulateLongTask {
setSendQueueEnabledResult(enabled)
}
override suspend fun ignoreDeviceTrustAndResend(devices: Map<UserId, List<DeviceId>>, sendHandle: SendHandle): Result<Unit> = simulateLongTask {
ignoreDeviceTrustAndResendResult(devices, sendHandle)
}
override suspend fun withdrawVerificationAndResend(userIds: List<UserId>, sendHandle: SendHandle): Result<Unit> = simulateLongTask {
withdrawVerificationAndResendResult(userIds, sendHandle)
}
private suspend fun simulateSendMediaProgress(progressCallback: ProgressCallback?) {
progressCallbackValues.forEach { (current, total) ->
progressCallback?.onProgress(current, total)
delay(1)
}
}
fun emitSyncUpdate() {
(syncUpdateFlow as MutableStateFlow).value = syncUpdateFlow.value + 1
}
}
@@ -0,0 +1,28 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.room.BaseRoom
import io.element.android.libraries.matrix.api.room.NotJoinedRoom
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
class FakeNotJoinedRoom(
override val localRoom: BaseRoom? = null,
override val previewInfo: RoomPreviewInfo = aRoomPreviewInfo(),
private val roomMembershipDetails: () -> Result<RoomMembershipDetails?> = { lambdaError() },
) : NotJoinedRoom {
override suspend fun membershipDetails(): Result<RoomMembershipDetails?> = simulateLongTask {
roomMembershipDetails()
}
override fun close() = Unit
}
@@ -0,0 +1,48 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.timeline.aMessageContent
import io.element.android.libraries.matrix.test.timeline.aProfileDetails
fun aRemoteLatestEvent(
content: EventContent = aMessageContent(),
timestamp: Long = 0L,
isOwn: Boolean = false,
senderId: UserId = A_USER_ID,
senderProfile: ProfileDetails = aProfileDetails(),
): LatestEventValue.Remote {
return LatestEventValue.Remote(
timestamp = timestamp,
content = content,
senderId = senderId,
senderProfile = senderProfile,
isOwn = isOwn,
)
}
fun aLocalLatestEvent(
content: EventContent = aMessageContent(),
timestamp: Long = 0L,
isSending: Boolean = false,
senderId: UserId = A_USER_ID,
senderProfile: ProfileDetails = aProfileDetails(),
): LatestEventValue.Local {
return LatestEventValue.Local(
timestamp = timestamp,
content = content,
senderId = senderId,
senderProfile = senderProfile,
isSending = isSending,
)
}
@@ -0,0 +1,107 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
fun aRoomInfo(
id: RoomId = A_ROOM_ID,
name: String? = A_ROOM_NAME,
rawName: String? = A_ROOM_RAW_NAME,
topic: String? = A_ROOM_TOPIC,
avatarUrl: String? = AN_AVATAR_URL,
isPublic: Boolean = true,
isDirect: Boolean = false,
isEncrypted: Boolean = false,
joinRule: JoinRule? = JoinRule.Public,
isSpace: Boolean = false,
successorRoom: SuccessorRoom? = null,
isFavorite: Boolean = false,
canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
inviter: RoomMember? = null,
activeMembersCount: Long = 2,
invitedMembersCount: Long = 1,
joinedMembersCount: Long = 1,
highlightCount: Long = 0,
notificationCount: Long = 0,
userDefinedNotificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
roomPowerLevels: RoomPowerLevels? = RoomPowerLevels(
values = defaultRoomPowerLevelValues(),
users = persistentMapOf(),
),
activeRoomCallParticipants: List<UserId> = emptyList(),
heroes: List<MatrixUser> = emptyList(),
pinnedEventIds: List<EventId> = emptyList(),
roomCreators: List<UserId> = emptyList(),
isMarkedUnread: Boolean = false,
numUnreadMessages: Long = 0,
numUnreadNotifications: Long = 0,
numUnreadMentions: Long = 0,
historyVisibility: RoomHistoryVisibility = RoomHistoryVisibility.Joined,
roomVersion: String? = "11",
privilegedCreatorRole: Boolean = false,
) = RoomInfo(
id = id,
name = name,
rawName = rawName,
topic = topic,
avatarUrl = avatarUrl,
isPublic = isPublic,
isDirect = isDirect,
isEncrypted = isEncrypted,
joinRule = joinRule,
isSpace = isSpace,
successorRoom = successorRoom,
isFavorite = isFavorite,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases.toImmutableList(),
currentUserMembership = currentUserMembership,
inviter = inviter,
activeMembersCount = activeMembersCount,
invitedMembersCount = invitedMembersCount,
joinedMembersCount = joinedMembersCount,
highlightCount = highlightCount,
notificationCount = notificationCount,
userDefinedNotificationMode = userDefinedNotificationMode,
hasRoomCall = hasRoomCall,
roomPowerLevels = roomPowerLevels,
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
heroes = heroes.toImmutableList(),
pinnedEventIds = pinnedEventIds.toImmutableList(),
creators = roomCreators.toImmutableList(),
isMarkedUnread = isMarkedUnread,
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions,
historyVisibility = historyVisibility,
roomVersion = roomVersion,
privilegedCreatorRole = privilegedCreatorRole,
)
@@ -0,0 +1,64 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomMembershipState
import kotlinx.collections.immutable.persistentListOf
fun aRoomMember(
userId: UserId = UserId("@alice:server.org"),
displayName: String? = null,
avatarUrl: String? = null,
membership: RoomMembershipState = RoomMembershipState.JOIN,
isNameAmbiguous: Boolean = false,
powerLevel: Long = 0L,
isIgnored: Boolean = false,
role: RoomMember.Role = RoomMember.Role.User,
membershipChangeReason: String? = null,
) = RoomMember(
userId = userId,
displayName = displayName,
avatarUrl = avatarUrl,
membership = membership,
isNameAmbiguous = isNameAmbiguous,
powerLevel = powerLevel,
isIgnored = isIgnored,
role = role,
membershipChangeReason = membershipChangeReason,
)
fun aRoomMemberList() = persistentListOf(
anAlice(),
aBob(),
aRoomMember(UserId("@carol:server.org"), "Carol"),
aRoomMember(UserId("@david:server.org"), "David"),
aRoomMember(UserId("@eve:server.org"), "Eve"),
aRoomMember(UserId("@justin:server.org"), "Justin"),
aRoomMember(UserId("@mallory:server.org"), "Mallory"),
aRoomMember(UserId("@susie:server.org"), "Susie"),
aVictor(),
aWalter(),
)
fun anAlice() = aRoomMember(UserId("@alice:server.org"), "Alice", role = RoomMember.Role.Admin)
fun aBob() = aRoomMember(UserId("@bob:server.org"), "Bob", role = RoomMember.Role.Moderator)
fun aVictor() = aRoomMember(
UserId("@victor:server.org"),
"Victor",
membership = RoomMembershipState.INVITE
)
fun aWalter() = aRoomMember(
UserId("@walter:server.org"),
"Walter",
membership = RoomMembershipState.INVITE
)
@@ -0,0 +1,56 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomMembershipDetails
import io.element.android.libraries.matrix.api.room.RoomType
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.preview.RoomPreviewInfo
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
import io.element.android.tests.testutils.lambda.lambdaError
fun aRoomPreview(
localRoom: FakeBaseRoom? = null,
info: RoomPreviewInfo = aRoomPreviewInfo(),
roomMembershipDetails: () -> Result<RoomMembershipDetails?> = { lambdaError() },
) = FakeNotJoinedRoom(
localRoom = localRoom,
previewInfo = info,
roomMembershipDetails = roomMembershipDetails,
)
fun aRoomPreviewInfo(
roomId: RoomId = A_ROOM_ID,
name: String? = A_ROOM_NAME,
topic: String? = A_ROOM_TOPIC,
avatarUrl: String? = AN_AVATAR_URL,
joinRule: JoinRule = JoinRule.Public,
isSpace: Boolean = false,
canonicalAlias: RoomAlias? = null,
currentUserMembership: CurrentUserMembership? = null,
numberOfJoinedMembers: Long = 1,
isHistoryWorldReadable: Boolean = true,
) = RoomPreviewInfo(
roomId = roomId,
name = name,
topic = topic,
avatarUrl = avatarUrl,
joinRule = joinRule,
canonicalAlias = canonicalAlias,
numberOfJoinedMembers = numberOfJoinedMembers,
roomType = if (isSpace) RoomType.Space else RoomType.Room,
isHistoryWorldReadable = isHistoryWorldReadable,
membership = currentUserMembership,
)
@@ -0,0 +1,120 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.CurrentUserMembership
import io.element.android.libraries.matrix.api.room.RoomInfo
import io.element.android.libraries.matrix.api.room.RoomMember
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
import io.element.android.libraries.matrix.api.room.history.RoomHistoryVisibility
import io.element.android.libraries.matrix.api.room.join.JoinRule
import io.element.android.libraries.matrix.api.room.powerlevels.RoomPowerLevels
import io.element.android.libraries.matrix.api.room.tombstone.SuccessorRoom
import io.element.android.libraries.matrix.api.roomlist.LatestEventValue
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.libraries.matrix.test.A_ROOM_NAME
import io.element.android.libraries.matrix.test.A_ROOM_RAW_NAME
import io.element.android.libraries.matrix.test.A_ROOM_TOPIC
import kotlinx.collections.immutable.persistentMapOf
import kotlinx.collections.immutable.toImmutableList
fun aRoomSummary(
info: RoomInfo = aRoomInfo(),
latestEventValue: LatestEventValue = aRemoteLatestEvent(),
) = RoomSummary(
info = info,
latestEvent = latestEventValue,
)
fun aRoomSummary(
roomId: RoomId = A_ROOM_ID,
name: String? = A_ROOM_NAME,
rawName: String? = A_ROOM_RAW_NAME,
topic: String? = A_ROOM_TOPIC,
avatarUrl: String? = null,
isPublic: Boolean = true,
isDirect: Boolean = false,
isEncrypted: Boolean = false,
joinRule: JoinRule? = JoinRule.Public,
isSpace: Boolean = false,
successorRoom: SuccessorRoom? = null,
isFavorite: Boolean = false,
canonicalAlias: RoomAlias? = null,
alternativeAliases: List<RoomAlias> = emptyList(),
currentUserMembership: CurrentUserMembership = CurrentUserMembership.JOINED,
inviter: RoomMember? = null,
activeMembersCount: Long = 1,
invitedMembersCount: Long = 0,
joinedMembersCount: Long = 1,
highlightCount: Long = 0,
notificationCount: Long = 0,
userDefinedNotificationMode: RoomNotificationMode? = null,
hasRoomCall: Boolean = false,
roomPowerLevels: RoomPowerLevels = RoomPowerLevels(
values = defaultRoomPowerLevelValues(),
users = persistentMapOf(),
),
activeRoomCallParticipants: List<UserId> = emptyList(),
heroes: List<MatrixUser> = emptyList(),
pinnedEventIds: List<EventId> = emptyList(),
roomCreators: List<UserId> = emptyList(),
isMarkedUnread: Boolean = false,
numUnreadMessages: Long = 0,
numUnreadNotifications: Long = 0,
numUnreadMentions: Long = 0,
historyVisibility: RoomHistoryVisibility = RoomHistoryVisibility.Joined,
latestEvent: LatestEventValue = aRemoteLatestEvent(),
roomVersion: String? = "11",
privilegedCreatorRole: Boolean = false,
) = RoomSummary(
info = RoomInfo(
id = roomId,
name = name,
rawName = rawName,
topic = topic,
avatarUrl = avatarUrl,
isPublic = isPublic,
isDirect = isDirect,
isEncrypted = isEncrypted,
joinRule = joinRule,
isSpace = isSpace,
successorRoom = successorRoom,
isFavorite = isFavorite,
canonicalAlias = canonicalAlias,
alternativeAliases = alternativeAliases.toImmutableList(),
currentUserMembership = currentUserMembership,
inviter = inviter,
activeMembersCount = activeMembersCount,
invitedMembersCount = invitedMembersCount,
joinedMembersCount = joinedMembersCount,
roomPowerLevels = roomPowerLevels,
highlightCount = highlightCount,
notificationCount = notificationCount,
userDefinedNotificationMode = userDefinedNotificationMode,
hasRoomCall = hasRoomCall,
activeRoomCallParticipants = activeRoomCallParticipants.toImmutableList(),
heroes = heroes.toImmutableList(),
pinnedEventIds = pinnedEventIds.toImmutableList(),
creators = roomCreators.toImmutableList(),
isMarkedUnread = isMarkedUnread,
numUnreadMessages = numUnreadMessages,
numUnreadNotifications = numUnreadNotifications,
numUnreadMentions = numUnreadMentions,
historyVisibility = historyVisibility,
roomVersion = roomVersion,
privilegedCreatorRole = privilegedCreatorRole,
),
latestEvent = latestEvent,
)
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room.alias
import io.element.android.libraries.matrix.api.core.RoomAlias
import io.element.android.libraries.matrix.api.room.alias.RoomAliasHelper
class FakeRoomAliasHelper(
private val roomAliasNameFromRoomDisplayNameLambda: (String) -> String = { name ->
name.trimStart().trimEnd().replace(" ", "_")
},
private val isRoomAliasValidLambda: (RoomAlias) -> Boolean = { true }
) : RoomAliasHelper {
override fun roomAliasNameFromRoomDisplayName(name: String): String {
return roomAliasNameFromRoomDisplayNameLambda(name)
}
override fun isRoomAliasValid(roomAlias: RoomAlias): Boolean {
return isRoomAliasValidLambda(roomAlias)
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room.join
import im.vector.app.features.analytics.plan.JoinedRoom
import io.element.android.libraries.matrix.api.core.RoomIdOrAlias
import io.element.android.libraries.matrix.api.room.join.JoinRoom
import io.element.android.tests.testutils.simulateLongTask
class FakeJoinRoom(
var lambda: (RoomIdOrAlias, List<String>, JoinedRoom.Trigger) -> Result<Unit>
) : JoinRoom {
override suspend fun invoke(
roomIdOrAlias: RoomIdOrAlias,
serverNames: List<String>,
trigger: JoinedRoom.Trigger,
): Result<Unit> = simulateLongTask {
lambda(roomIdOrAlias, serverNames, trigger)
}
}
@@ -0,0 +1,49 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.room.knock
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.room.knock.KnockRequest
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
class FakeKnockRequest(
override val eventId: EventId = AN_EVENT_ID,
override val userId: UserId = A_USER_ID,
override val displayName: String? = A_USER_NAME,
override val avatarUrl: String? = AN_AVATAR_URL,
override val reason: String? = null,
override val timestamp: Long? = null,
override val isSeen: Boolean = false,
val acceptLambda: () -> Result<Unit> = { lambdaError() },
val declineLambda: (String?) -> Result<Unit> = { lambdaError() },
val declineAndBanLambda: (String?) -> Result<Unit> = { lambdaError() },
val markAsSeenLambda: () -> Result<Unit> = { lambdaError() },
) : KnockRequest {
override suspend fun accept(): Result<Unit> = simulateLongTask {
acceptLambda()
}
override suspend fun decline(reason: String?): Result<Unit> = simulateLongTask {
declineLambda(reason)
}
override suspend fun declineAndBan(reason: String?): Result<Unit> = simulateLongTask {
declineAndBanLambda(reason)
}
override suspend fun markAsSeen(): Result<Unit> = simulateLongTask {
markAsSeenLambda()
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
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<RoomDirectoryList.SearchResult> = emptyFlow(),
val filterLambda: (String?, Int, String?) -> Result<Unit> = { _, _, _ -> Result.success(Unit) },
val loadMoreLambda: () -> Result<Unit> = { Result.success(Unit) }
) : RoomDirectoryList {
override suspend fun filter(filter: String?, batchSize: Int, viaServerName: String?): Result<Unit> = filterLambda(filter, batchSize, viaServerName)
override suspend fun loadMore(): Result<Unit> = loadMoreLambda()
}
@@ -0,0 +1,19 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.roomdirectory
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryList
import io.element.android.libraries.matrix.api.roomdirectory.RoomDirectoryService
import kotlinx.coroutines.CoroutineScope
class FakeRoomDirectoryService(
private val createRoomDirectoryListFactory: (CoroutineScope) -> RoomDirectoryList = { throw AssertionError("Configure a proper factory.") }
) : RoomDirectoryService {
override fun createRoomDirectoryList(scope: CoroutineScope) = createRoomDirectoryListFactory(scope)
}
@@ -0,0 +1,34 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.roomdirectory
import io.element.android.libraries.matrix.api.core.RoomAlias
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: RoomAlias? = 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,
numberOfMembers = joinedMembers
)
@@ -0,0 +1,67 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.roomlist
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
import io.element.android.libraries.matrix.api.roomlist.RoomList
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
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class FakeRoomListService(
var subscribeToVisibleRoomsLambda: (List<RoomId>) -> Unit = {},
) : RoomListService {
private val allRoomSummariesFlow = MutableStateFlow<List<RoomSummary>>(emptyList())
private val allRoomsLoadingStateFlow = MutableStateFlow<RoomList.LoadingState>(RoomList.LoadingState.NotLoaded)
private val roomListStateFlow = MutableStateFlow<RoomListService.State>(RoomListService.State.Idle)
private val syncIndicatorStateFlow = MutableStateFlow<RoomListService.SyncIndicator>(RoomListService.SyncIndicator.Hide)
suspend fun postAllRooms(roomSummaries: List<RoomSummary>) {
allRoomSummariesFlow.emit(roomSummaries)
}
suspend fun postAllRoomsLoadingState(loadingState: RoomList.LoadingState) {
allRoomsLoadingStateFlow.emit(loadingState)
}
suspend fun postState(state: RoomListService.State) {
roomListStateFlow.emit(state)
}
suspend fun postSyncIndicator(value: RoomListService.SyncIndicator) {
syncIndicatorStateFlow.emit(value)
}
override fun createRoomList(
pageSize: Int,
initialFilter: RoomListFilter,
source: RoomList.Source
): DynamicRoomList {
return when (source) {
RoomList.Source.All -> allRooms
}
}
override suspend fun subscribeToVisibleRooms(roomIds: List<RoomId>) {
subscribeToVisibleRoomsLambda(roomIds)
}
override val allRooms = SimplePagedRoomList(
allRoomSummariesFlow,
allRoomsLoadingStateFlow,
MutableStateFlow(RoomListFilter.all())
)
override val state: StateFlow<RoomListService.State> = roomListStateFlow
override val syncIndicator: StateFlow<RoomListService.SyncIndicator> = syncIndicatorStateFlow
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.roomlist
import io.element.android.libraries.matrix.api.roomlist.DynamicRoomList
import io.element.android.libraries.matrix.api.roomlist.RoomList
import io.element.android.libraries.matrix.api.roomlist.RoomListFilter
import io.element.android.libraries.matrix.api.roomlist.RoomSummary
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.getAndUpdate
data class SimplePagedRoomList(
override val summaries: MutableStateFlow<List<RoomSummary>>,
override val loadingState: StateFlow<RoomList.LoadingState>,
override val currentFilter: MutableStateFlow<RoomListFilter>
) : DynamicRoomList {
override val pageSize: Int = Int.MAX_VALUE
override val loadedPages = MutableStateFlow(1)
override val filteredSummaries: SharedFlow<List<RoomSummary>> = summaries
override suspend fun loadMore() {
// No-op
loadedPages.getAndUpdate { it + 1 }
}
override suspend fun reset() {
loadedPages.emit(1)
}
override suspend fun updateFilter(filter: RoomListFilter) {
currentFilter.emit(filter)
}
override suspend fun rebuildSummaries() {
// No-op
}
}
@@ -0,0 +1,35 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.spaces
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.LeaveSpaceHandle
import io.element.android.libraries.matrix.api.spaces.LeaveSpaceRoom
import io.element.android.libraries.matrix.test.A_SPACE_ID
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
class FakeLeaveSpaceHandle(
override val id: RoomId = A_SPACE_ID,
private val roomsResult: () -> Result<List<LeaveSpaceRoom>> = { lambdaError() },
private val leaveResult: (List<RoomId>) -> Result<Unit> = { lambdaError() },
private val closeResult: () -> Unit = { lambdaError() },
) : LeaveSpaceHandle {
override suspend fun rooms(): Result<List<LeaveSpaceRoom>> = simulateLongTask {
roomsResult()
}
override suspend fun leave(roomIds: List<RoomId>): Result<Unit> = simulateLongTask {
leaveResult(roomIds)
}
override fun close() {
return closeResult()
}
}
@@ -0,0 +1,58 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.spaces
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.test.A_ROOM_ID
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import java.util.Optional
class FakeSpaceRoomList(
override val roomId: RoomId = A_ROOM_ID,
initialSpaceFlowValue: SpaceRoom? = null,
initialSpaceRoomsValue: List<SpaceRoom> = emptyList(),
initialSpaceRoomList: SpaceRoomList.PaginationStatus = SpaceRoomList.PaginationStatus.Loading,
private val paginateResult: () -> Result<Unit> = { lambdaError() },
) : SpaceRoomList {
private val currentSpaceMutableStateFlow: MutableStateFlow<Optional<SpaceRoom>> = MutableStateFlow(Optional.ofNullable(initialSpaceFlowValue))
override val currentSpaceFlow: StateFlow<Optional<SpaceRoom>> = currentSpaceMutableStateFlow.asStateFlow()
fun emitCurrentSpace(value: SpaceRoom?) {
currentSpaceMutableStateFlow.value = Optional.ofNullable(value)
}
private val _spaceRoomsFlow: MutableStateFlow<List<SpaceRoom>> = MutableStateFlow(initialSpaceRoomsValue)
override val spaceRoomsFlow: Flow<List<SpaceRoom>> = _spaceRoomsFlow.asStateFlow()
fun emitSpaceRooms(value: List<SpaceRoom>) {
_spaceRoomsFlow.value = value
}
private val _paginationStatusFlow: MutableStateFlow<SpaceRoomList.PaginationStatus> = MutableStateFlow(initialSpaceRoomList)
override val paginationStatusFlow: StateFlow<SpaceRoomList.PaginationStatus> = _paginationStatusFlow.asStateFlow()
fun emitPaginationStatus(value: SpaceRoomList.PaginationStatus) {
_paginationStatusFlow.value = value
}
override suspend fun paginate(): Result<Unit> = simulateLongTask {
paginateResult()
}
override fun destroy() {
// No op
}
}
@@ -0,0 +1,46 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.spaces
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.spaces.LeaveSpaceHandle
import io.element.android.libraries.matrix.api.spaces.SpaceRoom
import io.element.android.libraries.matrix.api.spaces.SpaceRoomList
import io.element.android.libraries.matrix.api.spaces.SpaceService
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow
class FakeSpaceService(
private val joinedSpacesResult: () -> Result<List<SpaceRoom>> = { lambdaError() },
private val spaceRoomListResult: (RoomId) -> SpaceRoomList = { lambdaError() },
private val leaveSpaceHandleResult: (RoomId) -> LeaveSpaceHandle = { lambdaError() },
) : SpaceService {
private val _spaceRoomsFlow = MutableSharedFlow<List<SpaceRoom>>()
override val spaceRoomsFlow: SharedFlow<List<SpaceRoom>>
get() = _spaceRoomsFlow.asSharedFlow()
suspend fun emitSpaceRoomList(value: List<SpaceRoom>) {
_spaceRoomsFlow.emit(value)
}
override suspend fun joinedSpaces(): Result<List<SpaceRoom>> = simulateLongTask {
return joinedSpacesResult()
}
override fun spaceRoomList(id: RoomId): SpaceRoomList {
return spaceRoomListResult(id)
}
override fun getLeaveSpaceHandle(spaceId: RoomId): LeaveSpaceHandle {
return leaveSpaceHandleResult(spaceId)
}
}
@@ -0,0 +1,39 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.sync
import io.element.android.libraries.core.coroutine.mapState
import io.element.android.libraries.matrix.api.sync.SyncService
import io.element.android.libraries.matrix.api.sync.SyncState
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class FakeSyncService(
initialSyncState: SyncState = SyncState.Idle,
) : SyncService {
private val syncStateFlow: MutableStateFlow<SyncState> = MutableStateFlow(initialSyncState)
var startSyncLambda: () -> Result<Unit> = { Result.success(Unit) }
override suspend fun startSync(): Result<Unit> {
return startSyncLambda()
}
var stopSyncLambda: () -> Result<Unit> = { Result.success(Unit) }
override suspend fun stopSync(): Result<Unit> {
return stopSyncLambda()
}
override val syncState: StateFlow<SyncState> = syncStateFlow
override val isOnline: StateFlow<Boolean> = syncState.mapState { it != SyncState.Offline }
suspend fun emitSyncState(syncState: SyncState) {
syncStateFlow.emit(syncState)
}
}
@@ -0,0 +1,453 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.timeline
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.RoomId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.media.AudioInfo
import io.element.android.libraries.matrix.api.media.FileInfo
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaUploadHandler
import io.element.android.libraries.matrix.api.media.VideoInfo
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.room.IntentionalMention
import io.element.android.libraries.matrix.api.room.location.AssetType
import io.element.android.libraries.matrix.api.timeline.MatrixTimelineItem
import io.element.android.libraries.matrix.api.timeline.ReceiptType
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.item.event.EventOrTransactionId
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.MutableStateFlow
import java.io.File
class FakeTimeline(
private val name: String = "FakeTimeline",
override val timelineItems: Flow<List<MatrixTimelineItem>> = MutableStateFlow(emptyList()),
override val backwardPaginationStatus: MutableStateFlow<Timeline.PaginationStatus> = MutableStateFlow(
Timeline.PaginationStatus(
isPaginating = false,
hasMoreToLoad = true
)
),
override val forwardPaginationStatus: MutableStateFlow<Timeline.PaginationStatus> = MutableStateFlow(
Timeline.PaginationStatus(
isPaginating = false,
hasMoreToLoad = false
)
),
override val membershipChangeEventReceived: Flow<Unit> = MutableSharedFlow(),
override val onSyncedEventReceived: Flow<Unit> = MutableSharedFlow(),
private val cancelSendResult: (TransactionId) -> Result<Unit> = { lambdaError() },
override val mode: Timeline.Mode = Timeline.Mode.Live,
private val markAsReadResult: (ReceiptType) -> Result<Unit> = { lambdaError() },
private val getLatestEventIdResult: () -> Result<EventId?> = { lambdaError() },
var sendReadReceiptLambda: (
eventId: EventId,
receiptType: ReceiptType,
) -> Result<Unit> = { _, _ ->
lambdaError()
}
) : Timeline {
var sendMessageLambda: (
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
) -> Result<Unit> = { _, _, _ ->
lambdaError()
}
override suspend fun cancelSend(transactionId: TransactionId): Result<Unit> = simulateLongTask {
cancelSendResult(transactionId)
}
override suspend fun sendMessage(
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
): Result<Unit> = simulateLongTask {
sendMessageLambda(body, htmlBody, intentionalMentions)
}
var redactEventLambda: (eventOrTransactionId: EventOrTransactionId, reason: String?) -> Result<Unit> = { _, _ ->
lambdaError()
}
override suspend fun redactEvent(
eventOrTransactionId: EventOrTransactionId,
reason: String?
): Result<Unit> = redactEventLambda(eventOrTransactionId, reason)
var editMessageLambda: (
eventOrTransactionId: EventOrTransactionId,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
) -> Result<Unit> = { _, _, _, _ ->
lambdaError()
}
override suspend fun editMessage(
eventOrTransactionId: EventOrTransactionId,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
): Result<Unit> = editMessageLambda(
eventOrTransactionId,
body,
htmlBody,
intentionalMentions
)
var editCaptionLambda: (
eventOrTransactionId: EventOrTransactionId,
caption: String?,
formattedCaption: String?,
) -> Result<Unit> = { _, _, _ ->
lambdaError()
}
override suspend fun editCaption(
eventOrTransactionId: EventOrTransactionId,
caption: String?,
formattedCaption: String?,
): Result<Unit> = editCaptionLambda(
eventOrTransactionId,
caption,
formattedCaption,
)
var replyMessageLambda: (
inReplyToEventId: EventId?,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
fromNotification: Boolean,
) -> Result<Unit> = { _, _, _, _, _ ->
lambdaError()
}
override suspend fun replyMessage(
repliedToEventId: EventId,
body: String,
htmlBody: String?,
intentionalMentions: List<IntentionalMention>,
fromNotification: Boolean,
): Result<Unit> = replyMessageLambda(
repliedToEventId,
body,
htmlBody,
intentionalMentions,
fromNotification,
)
var sendImageLambda: (
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
body: String?,
formattedBody: String?,
inReplyToEventId: EventId??,
) -> Result<MediaUploadHandler> = { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendImage(
file: File,
thumbnailFile: File?,
imageInfo: ImageInfo,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId??,
): Result<MediaUploadHandler> = simulateLongTask {
sendImageLambda(
file,
thumbnailFile,
imageInfo,
caption,
formattedCaption,
inReplyToEventId,
)
}
var sendVideoLambda: (
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
body: String?,
formattedBody: String?,
inReplyToEventId: EventId??,
) -> Result<MediaUploadHandler> = { _, _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendVideo(
file: File,
thumbnailFile: File?,
videoInfo: VideoInfo,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId??,
): Result<MediaUploadHandler> = simulateLongTask {
sendVideoLambda(
file,
thumbnailFile,
videoInfo,
caption,
formattedCaption,
inReplyToEventId,
)
}
var sendAudioLambda: (
file: File,
audioInfo: AudioInfo,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId??,
) -> Result<MediaUploadHandler> = { _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendAudio(
file: File,
audioInfo: AudioInfo,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId??,
): Result<MediaUploadHandler> = simulateLongTask {
sendAudioLambda(
file,
audioInfo,
caption,
formattedCaption,
inReplyToEventId,
)
}
var sendFileLambda: (
file: File,
fileInfo: FileInfo,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId??,
) -> Result<MediaUploadHandler> = { _, _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendFile(
file: File,
fileInfo: FileInfo,
caption: String?,
formattedCaption: String?,
inReplyToEventId: EventId??,
): Result<MediaUploadHandler> = simulateLongTask {
sendFileLambda(
file,
fileInfo,
caption,
formattedCaption,
inReplyToEventId,
)
}
var sendVoiceMessageLambda: (
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
inReplyToEventId: EventId??,
) -> Result<MediaUploadHandler> = { _, _, _, _ ->
Result.success(FakeMediaUploadHandler())
}
override suspend fun sendVoiceMessage(
file: File,
audioInfo: AudioInfo,
waveform: List<Float>,
inReplyToEventId: EventId??,
): Result<MediaUploadHandler> = simulateLongTask {
sendVoiceMessageLambda(
file,
audioInfo,
waveform,
inReplyToEventId,
)
}
var sendLocationLambda: (
body: String,
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
inReplyToEventId: EventId??,
) -> Result<Unit> = { _, _, _, _, _, _ ->
lambdaError()
}
override suspend fun sendLocation(
body: String,
geoUri: String,
description: String?,
zoomLevel: Int?,
assetType: AssetType?,
inReplyToEventId: EventId??,
): Result<Unit> = simulateLongTask {
sendLocationLambda(
body,
geoUri,
description,
zoomLevel,
assetType,
inReplyToEventId,
)
}
var toggleReactionLambda: (emoji: String, eventOrTransactionId: EventOrTransactionId) -> Result<Boolean> = { _, _ -> lambdaError() }
override suspend fun toggleReaction(emoji: String, eventOrTransactionId: EventOrTransactionId): Result<Boolean> = simulateLongTask {
toggleReactionLambda(
emoji,
eventOrTransactionId,
)
}
var forwardEventLambda: (eventId: EventId, roomIds: List<RoomId>) -> Result<Unit> = { _, _ -> lambdaError() }
override suspend fun forwardEvent(eventId: EventId, roomIds: List<RoomId>): Result<Unit> = simulateLongTask {
forwardEventLambda(eventId, roomIds)
}
var createPollLambda: (
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
) -> Result<Unit> = { _, _, _, _ ->
lambdaError()
}
override suspend fun createPoll(question: String, answers: List<String>, maxSelections: Int, pollKind: PollKind): Result<Unit> = simulateLongTask {
createPollLambda(
question,
answers,
maxSelections,
pollKind,
)
}
var editPollLambda: (
pollStartId: EventId,
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind,
) -> Result<Unit> = { _, _, _, _, _ ->
lambdaError()
}
override suspend fun editPoll(
pollStartId: EventId,
question: String,
answers: List<String>,
maxSelections: Int,
pollKind: PollKind
): Result<Unit> = simulateLongTask {
editPollLambda(
pollStartId,
question,
answers,
maxSelections,
pollKind,
)
}
var sendPollResponseLambda: (
pollStartId: EventId,
answers: List<String>,
) -> Result<Unit> = { _, _ ->
lambdaError()
}
override suspend fun sendPollResponse(
pollStartId: EventId,
answers: List<String>,
): Result<Unit> = simulateLongTask {
sendPollResponseLambda(
pollStartId,
answers,
)
}
var endPollLambda: (
pollStartId: EventId,
text: String,
) -> Result<Unit> = { _, _ ->
lambdaError()
}
override suspend fun endPoll(
pollStartId: EventId,
text: String,
): Result<Unit> = simulateLongTask {
endPollLambda(
pollStartId,
text,
)
}
override suspend fun sendReadReceipt(
eventId: EventId,
receiptType: ReceiptType,
): Result<Unit> = sendReadReceiptLambda(eventId, receiptType)
override suspend fun markAsRead(receiptType: ReceiptType): Result<Unit> {
return markAsReadResult(receiptType)
}
var paginateLambda: (direction: Timeline.PaginationDirection) -> Result<Boolean> = {
Result.success(false)
}
override suspend fun paginate(direction: Timeline.PaginationDirection): Result<Boolean> = paginateLambda(direction)
var loadReplyDetailsLambda: (eventId: EventId) -> InReplyTo = {
InReplyTo.NotLoaded(it)
}
override suspend fun loadReplyDetails(eventId: EventId) = loadReplyDetailsLambda(eventId)
var pinEventLambda: (eventId: EventId) -> Result<Boolean> = { lambdaError() }
override suspend fun pinEvent(eventId: EventId): Result<Boolean> {
return pinEventLambda(eventId)
}
var unpinEventLambda: (eventId: EventId) -> Result<Boolean> = { lambdaError() }
override suspend fun unpinEvent(eventId: EventId): Result<Boolean> {
return unpinEventLambda(eventId)
}
override suspend fun getLatestEventId(): Result<EventId?> {
return getLatestEventIdResult()
}
var closeCounter = 0
private set
override fun close() {
closeCounter++
}
override fun toString() = "FakeTimeline: $name"
}
@@ -0,0 +1,25 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.timeline
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
class FakeTimelineProvider(
initialTimeline: Timeline? = null,
) : TimelineProvider {
private val timelineFlow = MutableStateFlow(initialTimeline)
override fun activeTimelineFlow(): StateFlow<Timeline?> {
return timelineFlow.asStateFlow()
}
}
@@ -0,0 +1,21 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2024, 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.timeline
import io.element.android.libraries.matrix.api.room.JoinedRoom
import io.element.android.libraries.matrix.api.timeline.Timeline
import io.element.android.libraries.matrix.api.timeline.TimelineProvider
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class LiveTimelineProvider(
private val room: JoinedRoom,
) : TimelineProvider {
override fun activeTimelineFlow(): StateFlow<Timeline> = MutableStateFlow(room.liveTimeline)
}
@@ -0,0 +1,159 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.timeline
import io.element.android.libraries.matrix.api.core.EventId
import io.element.android.libraries.matrix.api.core.TransactionId
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.media.ImageInfo
import io.element.android.libraries.matrix.api.media.MediaSource
import io.element.android.libraries.matrix.api.poll.PollAnswer
import io.element.android.libraries.matrix.api.poll.PollKind
import io.element.android.libraries.matrix.api.timeline.item.EventThreadInfo
import io.element.android.libraries.matrix.api.timeline.item.TimelineItemDebugInfo
import io.element.android.libraries.matrix.api.timeline.item.event.EventContent
import io.element.android.libraries.matrix.api.timeline.item.event.EventReaction
import io.element.android.libraries.matrix.api.timeline.item.event.EventTimelineItem
import io.element.android.libraries.matrix.api.timeline.item.event.InReplyTo
import io.element.android.libraries.matrix.api.timeline.item.event.LocalEventSendState
import io.element.android.libraries.matrix.api.timeline.item.event.MessageContent
import io.element.android.libraries.matrix.api.timeline.item.event.MessageShieldProvider
import io.element.android.libraries.matrix.api.timeline.item.event.MessageType
import io.element.android.libraries.matrix.api.timeline.item.event.PollContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileChangeContent
import io.element.android.libraries.matrix.api.timeline.item.event.ProfileDetails
import io.element.android.libraries.matrix.api.timeline.item.event.Receipt
import io.element.android.libraries.matrix.api.timeline.item.event.SendHandleProvider
import io.element.android.libraries.matrix.api.timeline.item.event.StickerContent
import io.element.android.libraries.matrix.api.timeline.item.event.TextMessageType
import io.element.android.libraries.matrix.api.timeline.item.event.TimelineItemDebugInfoProvider
import io.element.android.libraries.matrix.test.AN_EVENT_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.core.FakeSendHandle
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.ImmutableMap
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentMapOf
fun anEventTimelineItem(
eventId: EventId = AN_EVENT_ID,
transactionId: TransactionId? = null,
isEditable: Boolean = false,
canBeRepliedTo: Boolean = false,
isOwn: Boolean = false,
isRemote: Boolean = false,
localSendState: LocalEventSendState? = null,
reactions: ImmutableList<EventReaction> = persistentListOf(),
receipts: ImmutableList<Receipt> = persistentListOf(),
sender: UserId = A_USER_ID,
senderProfile: ProfileDetails = aProfileDetails(),
timestamp: Long = 0L,
content: EventContent = aProfileChangeMessageContent(),
debugInfoProvider: TimelineItemDebugInfoProvider = TimelineItemDebugInfoProvider { aTimelineItemDebugInfo() },
messageShieldProvider: MessageShieldProvider = MessageShieldProvider { null },
sendHandleProvider: SendHandleProvider = SendHandleProvider { FakeSendHandle() }
) = EventTimelineItem(
eventId = eventId,
transactionId = transactionId,
isEditable = isEditable,
canBeRepliedTo = canBeRepliedTo,
isOwn = isOwn,
isRemote = isRemote,
localSendState = localSendState,
reactions = reactions,
receipts = receipts,
sender = sender,
senderProfile = senderProfile,
timestamp = timestamp,
content = content,
origin = null,
timelineItemDebugInfoProvider = debugInfoProvider,
messageShieldProvider = messageShieldProvider,
sendHandleProvider = sendHandleProvider,
)
fun aProfileDetails(
displayName: String? = A_USER_NAME,
displayNameAmbiguous: Boolean = false,
avatarUrl: String? = null
): ProfileDetails = ProfileDetails.Ready(
displayName = displayName,
displayNameAmbiguous = displayNameAmbiguous,
avatarUrl = avatarUrl,
)
fun aProfileChangeMessageContent(
displayName: String? = null,
prevDisplayName: String? = null,
avatarUrl: String? = null,
prevAvatarUrl: String? = null,
) = ProfileChangeContent(
displayName = displayName,
prevDisplayName = prevDisplayName,
avatarUrl = avatarUrl,
prevAvatarUrl = prevAvatarUrl,
)
fun aMessageContent(
body: String = "body",
inReplyTo: InReplyTo? = null,
isEdited: Boolean = false,
threadInfo: EventThreadInfo? = null,
messageType: MessageType = TextMessageType(
body = body,
formatted = null
)
) = MessageContent(
body = body,
inReplyTo = inReplyTo,
isEdited = isEdited,
threadInfo = threadInfo,
type = messageType
)
fun aStickerContent(
filename: String = "filename",
info: ImageInfo,
mediaSource: MediaSource,
body: String? = null,
) = StickerContent(
filename = filename,
body = body,
info = info,
source = mediaSource,
)
fun aTimelineItemDebugInfo(
model: String = "Rust(Model())",
originalJson: String? = null,
latestEditedJson: String? = null,
) = TimelineItemDebugInfo(
model,
originalJson,
latestEditedJson
)
fun aPollContent(
question: String = "Do you like polls?",
answers: ImmutableList<PollAnswer> = persistentListOf(PollAnswer("1", "Yes"), PollAnswer("2", "No")),
kind: PollKind = PollKind.Disclosed,
maxSelections: ULong = 1u,
votes: ImmutableMap<String, ImmutableList<UserId>> = persistentMapOf(),
endTime: ULong? = null,
isEdited: Boolean = false,
) = PollContent(
question = question,
kind = kind,
maxSelections = maxSelections,
answers = answers,
votes = votes,
endTime = endTime,
isEdited = isEdited,
)
@@ -0,0 +1,26 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.timeline.item.event
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.timeline.item.event.MembershipChange
import io.element.android.libraries.matrix.api.timeline.item.event.RoomMembershipContent
import io.element.android.libraries.matrix.test.A_USER_ID
fun aRoomMembershipContent(
userId: UserId = A_USER_ID,
userDisplayName: String? = null,
change: MembershipChange? = null,
reason: String? = null,
) = RoomMembershipContent(
userId = userId,
userDisplayName = userDisplayName,
change = change,
reason = reason,
)
@@ -0,0 +1,27 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.tracing
import io.element.android.libraries.matrix.api.tracing.TracingService
import io.element.android.libraries.matrix.api.tracing.WriteToFilesConfiguration
import io.element.android.tests.testutils.lambda.lambdaError
import timber.log.Timber
class FakeTracingService(
private val createTimberTreeResult: (String) -> Timber.Tree = { lambdaError() },
private val updateWriteToFilesConfigurationResult: (WriteToFilesConfiguration) -> Unit = { lambdaError() }
) : TracingService {
override fun createTimberTree(target: String): Timber.Tree {
return createTimberTreeResult(target)
}
override fun updateWriteToFilesConfiguration(config: WriteToFilesConfiguration) {
updateWriteToFilesConfigurationResult(config)
}
}
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.verification
import io.element.android.libraries.matrix.api.core.UserId
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import io.element.android.libraries.matrix.api.verification.SessionVerificationServiceListener
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
import io.element.android.libraries.matrix.api.verification.VerificationFlowState
import io.element.android.libraries.matrix.api.verification.VerificationRequest
import io.element.android.tests.testutils.lambda.lambdaError
import io.element.android.tests.testutils.simulateLongTask
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
class FakeSessionVerificationService(
initialSessionVerifiedStatus: SessionVerifiedStatus = SessionVerifiedStatus.Unknown,
private val requestCurrentSessionVerificationLambda: () -> Unit = { lambdaError() },
private val requestUserVerificationLambda: (UserId) -> Unit = { lambdaError() },
private val cancelVerificationLambda: () -> Unit = { lambdaError() },
private val approveVerificationLambda: () -> Unit = { lambdaError() },
private val declineVerificationLambda: () -> Unit = { lambdaError() },
private val startVerificationLambda: () -> Unit = { lambdaError() },
private val resetLambda: (Boolean) -> Unit = { lambdaError() },
private val acknowledgeVerificationRequestLambda: (VerificationRequest.Incoming) -> Unit = { lambdaError() },
private val acceptVerificationRequestLambda: () -> Unit = { lambdaError() },
) : SessionVerificationService {
private val _sessionVerifiedStatus = MutableStateFlow(initialSessionVerifiedStatus)
private var _verificationFlowState = MutableStateFlow<VerificationFlowState>(VerificationFlowState.Initial)
private var _needsSessionVerification = MutableStateFlow(true)
override val verificationFlowState: StateFlow<VerificationFlowState> = _verificationFlowState
override val sessionVerifiedStatus: StateFlow<SessionVerifiedStatus> = _sessionVerifiedStatus
override val needsSessionVerification: Flow<Boolean> = _needsSessionVerification
override suspend fun requestCurrentSessionVerification() {
requestCurrentSessionVerificationLambda()
}
override suspend fun requestUserVerification(userId: UserId) {
requestUserVerificationLambda(userId)
}
override suspend fun cancelVerification() {
cancelVerificationLambda()
}
override suspend fun approveVerification() {
approveVerificationLambda()
}
override suspend fun declineVerification() {
declineVerificationLambda()
}
override suspend fun startVerification() {
startVerificationLambda()
}
override suspend fun reset(cancelAnyPendingVerificationAttempt: Boolean) {
resetLambda(cancelAnyPendingVerificationAttempt)
}
var listener: SessionVerificationServiceListener? = null
private set
override fun setListener(listener: SessionVerificationServiceListener?) {
this.listener = listener
}
override suspend fun acknowledgeVerificationRequest(verificationRequest: VerificationRequest.Incoming) {
acknowledgeVerificationRequestLambda(verificationRequest)
}
override suspend fun acceptVerificationRequest() = simulateLongTask {
acceptVerificationRequestLambda()
}
suspend fun emitVerificationFlowState(state: VerificationFlowState) {
_verificationFlowState.emit(state)
}
suspend fun emitVerifiedStatus(status: SessionVerifiedStatus) {
_sessionVerifiedStatus.emit(status)
}
suspend fun emitNeedsSessionVerification(needsVerification: Boolean) {
_needsSessionVerification.emit(needsVerification)
}
}
@@ -0,0 +1,29 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.widget
import io.element.android.libraries.matrix.api.widget.CallWidgetSettingsProvider
import io.element.android.libraries.matrix.api.widget.MatrixWidgetSettings
class FakeCallWidgetSettingsProvider(
private val provideFn: (String, String, Boolean, Boolean, Boolean) -> MatrixWidgetSettings = { _, _, _, _, _ -> MatrixWidgetSettings("id", true, "url") }
) : CallWidgetSettingsProvider {
val providedBaseUrls = mutableListOf<String>()
override suspend fun provide(
baseUrl: String,
widgetId: String,
encrypted: Boolean,
direct: Boolean,
hasActiveCall: Boolean
): MatrixWidgetSettings {
providedBaseUrls += baseUrl
return provideFn(baseUrl, widgetId, encrypted, direct, hasActiveCall)
}
}
@@ -0,0 +1,43 @@
/*
* Copyright (c) 2025 Element Creations Ltd.
* Copyright 2023-2025 New Vector Ltd.
*
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
* Please see LICENSE files in the repository root for full details.
*/
package io.element.android.libraries.matrix.test.widget
import io.element.android.libraries.matrix.api.widget.MatrixWidgetDriver
import kotlinx.coroutines.flow.MutableSharedFlow
import java.util.UUID
class FakeMatrixWidgetDriver(
override val id: String = UUID.randomUUID().toString(),
) : MatrixWidgetDriver {
private val _sentMessages = mutableListOf<String>()
val sentMessages: List<String> = _sentMessages
var runCalledCount = 0
private set
var closeCalledCount = 0
private set
override val incomingMessages = MutableSharedFlow<String>(extraBufferCapacity = 1)
override suspend fun run() {
runCalledCount++
}
override suspend fun send(message: String) {
_sentMessages.add(message)
}
override fun close() {
closeCalledCount++
}
fun givenIncomingMessage(message: String) {
incomingMessages.tryEmit(message)
}
}