From 5bda29ca7e6fbd362431626bab7fd1ca7cd0057c Mon Sep 17 00:00:00 2001 From: Benoit Marty Date: Mon, 22 Jul 2024 10:39:48 +0200 Subject: [PATCH] Rework FakeMatrixRoom so that it contains only lambdas. (#3229) * Upgrade lint to 8.7.0-alpha01 * FakeMatrixRoom: lambda everywhere Fix test compilation issues --- .../appnav/JoinRoomLoadedFlowNodeTest.kt | 8 +- .../android/appnav/loggedin/SendQueuesTest.kt | 16 +- .../utils/DefaultCallWidgetProviderTest.kt | 38 +- .../AcceptDeclineInvitePresenterTest.kt | 8 +- .../impl/DefaultLeaveRoomPresenterTest.kt | 20 +- .../impl/send/SendLocationPresenterTest.kt | 74 ++- .../messages/impl/MessagesPresenterTest.kt | 186 ++++++- .../AttachmentsPreviewPresenterTest.kt | 26 +- .../impl/report/ReportMessagePresenterTest.kt | 30 +- .../MessageComposerPresenterTest.kt | 142 +++-- .../impl/timeline/TimelineControllerTest.kt | 31 +- .../impl/timeline/TimelinePresenterTest.kt | 28 +- .../VoiceMessageComposerPresenterTest.kt | 26 +- .../impl/create/CreatePollPresenterTest.kt | 54 +- .../features/roomdetails/MatrixRoomFixture.kt | 26 +- .../roomdetails/RoomDetailsPresenterTest.kt | 283 +++++++--- .../edit/RoomDetailsEditPresenterTest.kt | 169 +++--- .../members/RoomMemberListPresenterTest.kt | 71 ++- .../details/RoomMemberDetailsPresenterTest.kt | 88 ++- ...faultRoomMembersModerationPresenterTest.kt | 104 ++-- .../RolesAndPermissionPresenterTest.kt | 21 +- .../changeroles/ChangeRolesPresenterTest.kt | 15 +- .../ChangeRoomPermissionsPresenterTest.kt | 25 +- .../roomlist/impl/RoomListPresenterTest.kt | 13 +- .../features/share/impl/SharePresenterTest.kt | 9 +- gradle.properties | 2 +- .../matrix/test/room/FakeMatrixRoom.kt | 523 +++++------------- libraries/mediaupload/api/build.gradle.kts | 1 + .../mediaupload/api/MediaSenderTest.kt | 23 +- ...otificationBroadcastReceiverHandlerTest.kt | 11 +- 30 files changed, 1253 insertions(+), 818 deletions(-) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt index d307f851cd..b1009f20da 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/JoinRoomLoadedFlowNodeTest.kt @@ -123,7 +123,9 @@ class JoinRoomLoadedFlowNodeTest { @Test fun `given a room flow node when initialized then it loads messages entry point`() = runTest { // GIVEN - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + updateMembersResult = { } + ) val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) val roomFlowNode = createJoinedRoomLoadedFlowNode( @@ -144,7 +146,9 @@ class JoinRoomLoadedFlowNodeTest { @Test fun `given a room flow node when callback on room details is triggered then it loads room details entry point`() = runTest { // GIVEN - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + updateMembersResult = { } + ) val fakeMessagesEntryPoint = FakeMessagesEntryPoint() val fakeRoomDetailsEntryPoint = FakeRoomDetailsEntryPoint() val inputs = JoinedRoomLoadedFlowNode.Inputs(room, RoomNavigationTarget.Messages()) diff --git a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt index bb4e8f3dbf..91f0aee517 100644 --- a/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt +++ b/appnav/src/test/kotlin/io/element/android/appnav/loggedin/SendQueuesTest.kt @@ -33,7 +33,6 @@ import org.junit.Test @OptIn(ExperimentalCoroutinesApi::class) class SendQueuesTest { private val matrixClient = FakeMatrixClient() - private val room = FakeMatrixRoom() private val networkMonitor = FakeNetworkMonitor() private val sut = SendQueues(matrixClient, networkMonitor) @@ -43,11 +42,11 @@ import org.junit.Test val setAllSendQueuesEnabledLambda = lambdaRecorder { _: Boolean -> } matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda - matrixClient.givenGetRoomResult(room.roomId, room) - val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> } - room.setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda - + val room = FakeMatrixRoom( + setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda + ) + matrixClient.givenGetRoomResult(room.roomId, room) sut.launchIn(backgroundScope) sendQueueDisabledFlow.emit(room.roomId) @@ -72,10 +71,11 @@ import org.junit.Test matrixClient.sendQueueDisabledFlow = sendQueueDisabledFlow matrixClient.setAllSendQueuesEnabledLambda = setAllSendQueuesEnabledLambda networkMonitor.connectivity.value = NetworkStatus.Offline - matrixClient.givenGetRoomResult(room.roomId, room) - val setRoomSendQueueEnabledLambda = lambdaRecorder { _: Boolean -> } - room.setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda + val room = FakeMatrixRoom( + setSendQueueEnabledLambda = setRoomSendQueueEnabledLambda + ) + matrixClient.givenGetRoomResult(room.roomId, room) sut.launchIn(backgroundScope) diff --git a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt index 7eb426c35e..a0c2f9a80e 100644 --- a/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt +++ b/features/call/impl/src/test/kotlin/io/element/android/features/call/utils/DefaultCallWidgetProviderTest.kt @@ -54,9 +54,9 @@ class DefaultCallWidgetProviderTest { @Test fun `getWidget - fails if it can't generate the URL for the widget`() = runTest { - val room = FakeMatrixRoom().apply { - givenGenerateWidgetWebViewUrlResult(Result.failure(Exception("Can't generate URL for widget"))) - } + val room = FakeMatrixRoom( + generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.failure(Exception("Can't generate URL for widget")) } + ) val client = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, room) } @@ -66,10 +66,10 @@ class DefaultCallWidgetProviderTest { @Test fun `getWidget - fails if it can't get the widget driver`() = runTest { - val room = FakeMatrixRoom().apply { - givenGenerateWidgetWebViewUrlResult(Result.success("url")) - givenGetWidgetDriverResult(Result.failure(Exception("Can't get a widget driver"))) - } + val room = FakeMatrixRoom( + generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") }, + getWidgetDriverResult = { Result.failure(Exception("Can't get a widget driver")) } + ) val client = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, room) } @@ -79,10 +79,10 @@ class DefaultCallWidgetProviderTest { @Test fun `getWidget - returns a widget driver when all steps are successful`() = runTest { - val room = FakeMatrixRoom().apply { - givenGenerateWidgetWebViewUrlResult(Result.success("url")) - givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver())) - } + val room = FakeMatrixRoom( + generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") }, + getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) }, + ) val client = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, room) } @@ -92,10 +92,10 @@ class DefaultCallWidgetProviderTest { @Test fun `getWidget - will use a custom base url if it exists`() = runTest { - val room = FakeMatrixRoom().apply { - givenGenerateWidgetWebViewUrlResult(Result.success("url")) - givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver())) - } + val room = FakeMatrixRoom( + generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") }, + getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) }, + ) val client = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, room) } @@ -120,10 +120,10 @@ class DefaultCallWidgetProviderTest { val elementCallBaseUrlProvider = FakeElementCallBaseUrlProvider { matrixClient -> providesLambda(matrixClient) } - val room = FakeMatrixRoom().apply { - givenGenerateWidgetWebViewUrlResult(Result.success("url")) - givenGetWidgetDriverResult(Result.success(FakeMatrixWidgetDriver())) - } + val room = FakeMatrixRoom( + generateWidgetWebViewUrlResult = { _, _, _, _ -> Result.success("url") }, + getWidgetDriverResult = { Result.success(FakeMatrixWidgetDriver()) }, + ) val client = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, room) } diff --git a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt index 9a12e33b33..39a0321b01 100644 --- a/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt +++ b/features/invite/impl/src/test/kotlin/io/element/android/features/invite/impl/response/AcceptDeclineInvitePresenterTest.kt @@ -92,9 +92,9 @@ class AcceptDeclineInvitePresenterTest { val client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom().apply { + result = FakeMatrixRoom( leaveRoomLambda = declineInviteFailure - } + ) ) } val presenter = createAcceptDeclineInvitePresenter(client = client) @@ -142,9 +142,9 @@ class AcceptDeclineInvitePresenterTest { val client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom().apply { + result = FakeMatrixRoom( leaveRoomLambda = declineInviteSuccess - } + ) ) } val presenter = createAcceptDeclineInvitePresenter( diff --git a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt index 49b44851de..a063b9ca90 100644 --- a/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt +++ b/features/leaveroom/impl/src/test/kotlin/io/element/android/features/leaveroom/impl/DefaultLeaveRoomPresenterTest.kt @@ -140,7 +140,9 @@ class DefaultLeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom(), + result = FakeMatrixRoom( + leaveRoomLambda = { Result.success(Unit) } + ), ) }, roomMembershipObserver = roomMembershipObserver @@ -162,9 +164,9 @@ class DefaultLeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom().apply { - this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) } - }, + result = FakeMatrixRoom( + leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) } + ), ) } ) @@ -186,7 +188,9 @@ class DefaultLeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom(), + result = FakeMatrixRoom( + leaveRoomLambda = { Result.success(Unit) } + ), ) } ) @@ -208,9 +212,9 @@ class DefaultLeaveRoomPresenterTest { client = FakeMatrixClient().apply { givenGetRoomResult( roomId = A_ROOM_ID, - result = FakeMatrixRoom().apply { - this.leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) } - }, + result = FakeMatrixRoom( + leaveRoomLambda = { Result.failure(RuntimeException("Blimey!")) } + ), ) } ) diff --git a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt index 93e15f7868..24fcd3537c 100644 --- a/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt +++ b/features/location/impl/src/test/kotlin/io/element/android/features/location/impl/send/SendLocationPresenterTest.kt @@ -29,13 +29,15 @@ import io.element.android.features.location.impl.common.permissions.PermissionsE import io.element.android.features.location.impl.common.permissions.PermissionsPresenter import io.element.android.features.location.impl.common.permissions.PermissionsState import io.element.android.features.messages.test.FakeMessageComposerContext +import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.location.AssetType import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeMatrixRoom -import io.element.android.libraries.matrix.test.room.SendLocationInvocation import io.element.android.libraries.textcomposer.model.MessageComposerMode import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import kotlinx.coroutines.delay import kotlinx.coroutines.test.runTest import org.junit.Rule @@ -46,16 +48,18 @@ class SendLocationPresenterTest { val warmUpRule = WarmUpRule() private val fakePermissionsPresenter = FakePermissionsPresenter() - private val fakeMatrixRoom = FakeMatrixRoom() private val fakeAnalyticsService = FakeAnalyticsService() private val fakeMessageComposerContext = FakeMessageComposerContext() private val fakeLocationActions = FakeLocationActions() private val fakeBuildMeta = aBuildMeta(applicationName = "app name") - private val sendLocationPresenter: SendLocationPresenter = SendLocationPresenter( + + private fun createSendLocationPresenter( + matrixRoom: MatrixRoom = FakeMatrixRoom(), + ): SendLocationPresenter = SendLocationPresenter( permissionsPresenterFactory = object : PermissionsPresenter.Factory { override fun create(permissions: List): PermissionsPresenter = fakePermissionsPresenter }, - room = fakeMatrixRoom, + room = matrixRoom, analyticsService = fakeAnalyticsService, messageComposerContext = fakeMessageComposerContext, locationActions = fakeLocationActions, @@ -64,6 +68,7 @@ class SendLocationPresenterTest { @Test fun `initial state with permissions granted`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.AllGranted, @@ -90,6 +95,7 @@ class SendLocationPresenterTest { @Test fun `initial state with permissions partially granted`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.SomeGranted, @@ -116,6 +122,7 @@ class SendLocationPresenterTest { @Test fun `initial state with permissions denied`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.NoneGranted, @@ -142,6 +149,7 @@ class SendLocationPresenterTest { @Test fun `initial state with permissions denied once`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.NoneGranted, @@ -168,6 +176,7 @@ class SendLocationPresenterTest { @Test fun `rationale dialog dismiss`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.NoneGranted, @@ -199,6 +208,7 @@ class SendLocationPresenterTest { @Test fun `rationale dialog continue`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.NoneGranted, @@ -227,6 +237,7 @@ class SendLocationPresenterTest { @Test fun `permission denied dialog dismiss`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.NoneGranted, @@ -258,6 +269,13 @@ class SendLocationPresenterTest { @Test fun `share sender location`() = runTest { + val sendLocationResult = lambdaRecorder> { _, _, _, _, _ -> + Result.success(Unit) + } + val matrixRoom = FakeMatrixRoom( + sendLocationResult = sendLocationResult, + ) + val sendLocationPresenter = createSendLocationPresenter(matrixRoom) fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.AllGranted, @@ -289,16 +307,14 @@ class SendLocationPresenterTest { delay(1) // Wait for the coroutine to finish - assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1) - assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo( - SendLocationInvocation( - body = "Location was shared at geo:3.0,4.0;u=5.0", - geoUri = "geo:3.0,4.0;u=5.0", - description = null, - zoomLevel = 15, - assetType = AssetType.SENDER + sendLocationResult.assertions().isCalledOnce() + .with( + value("Location was shared at geo:3.0,4.0;u=5.0"), + value("geo:3.0,4.0;u=5.0"), + value(null), + value(15), + value(AssetType.SENDER), ) - ) assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1) assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo( @@ -314,6 +330,13 @@ class SendLocationPresenterTest { @Test fun `share pin location`() = runTest { + val sendLocationResult = lambdaRecorder> { _, _, _, _, _ -> + Result.success(Unit) + } + val matrixRoom = FakeMatrixRoom( + sendLocationResult = sendLocationResult, + ) + val sendLocationPresenter = createSendLocationPresenter(matrixRoom) fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.NoneGranted, @@ -345,16 +368,14 @@ class SendLocationPresenterTest { delay(1) // Wait for the coroutine to finish - assertThat(fakeMatrixRoom.sentLocations.size).isEqualTo(1) - assertThat(fakeMatrixRoom.sentLocations.last()).isEqualTo( - SendLocationInvocation( - body = "Location was shared at geo:0.0,1.0", - geoUri = "geo:0.0,1.0", - description = null, - zoomLevel = 15, - assetType = AssetType.PIN + sendLocationResult.assertions().isCalledOnce() + .with( + value("Location was shared at geo:0.0,1.0"), + value("geo:0.0,1.0"), + value(null), + value(15), + value(AssetType.PIN), ) - ) assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(1) assertThat(fakeAnalyticsService.capturedEvents.last()).isEqualTo( @@ -370,6 +391,13 @@ class SendLocationPresenterTest { @Test fun `composer context passes through analytics`() = runTest { + val sendLocationResult = lambdaRecorder> { _, _, _, _, _ -> + Result.success(Unit) + } + val matrixRoom = FakeMatrixRoom( + sendLocationResult = sendLocationResult, + ) + val sendLocationPresenter = createSendLocationPresenter(matrixRoom) fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.NoneGranted, @@ -418,6 +446,7 @@ class SendLocationPresenterTest { @Test fun `open settings activity`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() fakePermissionsPresenter.givenState( aPermissionsState( permissions = PermissionsState.Permissions.NoneGranted, @@ -452,6 +481,7 @@ class SendLocationPresenterTest { @Test fun `application name is in state`() = runTest { + val sendLocationPresenter = createSendLocationPresenter() moleculeFlow(RecompositionMode.Immediate) { sendLocationPresenter.present() }.test { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt index 2d08b8d4a2..091305893e 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/MessagesPresenterTest.kt @@ -65,6 +65,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService 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.MediaSource import io.element.android.libraries.matrix.api.permalink.PermalinkParser import io.element.android.libraries.matrix.api.room.MatrixRoom @@ -103,6 +104,7 @@ import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.consumeItemsUntilTimeout import io.element.android.tests.testutils.lambda.assert +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.testCoroutineDispatchers @@ -136,7 +138,7 @@ class MessagesPresenterTest { assertThat(initialState.roomAvatar) .isEqualTo(AsyncData.Success(AvatarData(id = A_ROOM_ID.value, name = "", url = AN_AVATAR_URL, size = AvatarSize.TimelineRoom))) assertThat(initialState.userHasPermissionToSendMessage).isTrue() - assertThat(initialState.userHasPermissionToRedactOwn).isFalse() + assertThat(initialState.userHasPermissionToRedactOwn).isTrue() assertThat(initialState.hasNetworkConnection).isTrue() assertThat(initialState.snackbarMessage).isNull() assertThat(initialState.inviteProgress).isEqualTo(AsyncData.Uninitialized) @@ -147,7 +149,13 @@ class MessagesPresenterTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `present - check that the room's unread flag is removed`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) assertThat(room.markAsReadCalls).isEmpty() val presenter = createMessagesPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { @@ -161,8 +169,13 @@ class MessagesPresenterTest { @Test fun `present - call is disabled if user cannot join it even if there is an ongoing call`() = runTest { - val room = FakeMatrixRoom().apply { - givenCanUserJoinCall(Result.success(false)) + val room = FakeMatrixRoom( + canUserJoinCallResult = { Result.success(false) }, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ).apply { givenRoomInfo(aRoomInfo(hasRoomCall = true)) } val presenter = createMessagesPresenter(matrixRoom = room) @@ -183,7 +196,14 @@ class MessagesPresenterTest { val timeline = FakeTimeline().apply { this.toggleReactionLambda = toggleReactionSuccess } - val room = FakeMatrixRoom(liveTimeline = timeline) + val room = FakeMatrixRoom( + liveTimeline = timeline, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -213,7 +233,14 @@ class MessagesPresenterTest { val timeline = FakeTimeline().apply { this.toggleReactionLambda = toggleReactionSuccess } - val room = FakeMatrixRoom(liveTimeline = timeline) + val room = FakeMatrixRoom( + liveTimeline = timeline, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = room, coroutineDispatchers = coroutineDispatchers) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -266,6 +293,11 @@ class MessagesPresenterTest { val event = aMessageEvent() val matrixRoom = FakeMatrixRoom( eventPermalinkResult = { Result.success("a link") }, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, ) val presenter = createMessagesPresenter( clipboardHelper = clipboardHelper, @@ -448,7 +480,14 @@ class MessagesPresenterTest { val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true) val liveTimeline = FakeTimeline() - val matrixRoom = FakeMatrixRoom(liveTimeline = liveTimeline) + val matrixRoom = FakeMatrixRoom( + liveTimeline = liveTimeline, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(true) } liveTimeline.redactEventLambda = redactEventLambda @@ -513,7 +552,16 @@ class MessagesPresenterTest { @Test fun `present - shows prompt to reinvite users in DM`() = runTest { - val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 1L) + val room = FakeMatrixRoom( + sessionId = A_SESSION_ID, + isDirect = true, + activeMemberCount = 1L, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -539,7 +587,16 @@ class MessagesPresenterTest { @Test fun `present - doesn't show reinvite prompt in non-direct room`() = runTest { - val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = false, activeMemberCount = 1L) + val room = FakeMatrixRoom( + sessionId = A_SESSION_ID, + isDirect = false, + activeMemberCount = 1L, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -554,7 +611,16 @@ class MessagesPresenterTest { @Test fun `present - doesn't show reinvite prompt if other party is present`() = runTest { - val room = FakeMatrixRoom(sessionId = A_SESSION_ID, isDirect = true, activeMemberCount = 2L) + val room = FakeMatrixRoom( + sessionId = A_SESSION_ID, + isDirect = true, + activeMemberCount = 2L, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -569,7 +635,16 @@ class MessagesPresenterTest { @Test fun `present - handle reinviting other user when memberlist is ready`() = runTest { - val room = FakeMatrixRoom(sessionId = A_SESSION_ID) + val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) } + val room = FakeMatrixRoom( + sessionId = A_SESSION_ID, + inviteUserResult = inviteUserResult, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) room.givenRoomMembersState( MatrixRoomMembersState.Ready( persistentListOf( @@ -589,13 +664,22 @@ class MessagesPresenterTest { assertThat(loadingState.inviteProgress.isLoading()).isTrue() val newState = awaitItem() assertThat(newState.inviteProgress.isSuccess()).isTrue() - assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2) + inviteUserResult.assertions().isCalledOnce().with(value(A_SESSION_ID_2)) } } @Test fun `present - handle reinviting other user when memberlist is error`() = runTest { - val room = FakeMatrixRoom(sessionId = A_SESSION_ID) + val inviteUserResult = lambdaRecorder { _: UserId -> Result.success(Unit) } + val room = FakeMatrixRoom( + sessionId = A_SESSION_ID, + inviteUserResult = inviteUserResult, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) room.givenRoomMembersState( MatrixRoomMembersState.Error( failure = Throwable(), @@ -618,13 +702,20 @@ class MessagesPresenterTest { assertThat(loadingState.inviteProgress.isLoading()).isTrue() val newState = awaitItem() assertThat(newState.inviteProgress.isSuccess()).isTrue() - assertThat(room.invitedUserId).isEqualTo(A_SESSION_ID_2) + inviteUserResult.assertions().isCalledOnce().with(value(A_SESSION_ID_2)) } } @Test fun `present - handle reinviting other user when memberlist is not ready`() = runTest { - val room = FakeMatrixRoom(sessionId = A_SESSION_ID) + val room = FakeMatrixRoom( + sessionId = A_SESSION_ID, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) room.givenRoomMembersState(MatrixRoomMembersState.Unknown) val presenter = createMessagesPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { @@ -642,7 +733,15 @@ class MessagesPresenterTest { @Test fun `present - handle reinviting other user when inviting fails`() = runTest { - val room = FakeMatrixRoom(sessionId = A_SESSION_ID) + val room = FakeMatrixRoom( + sessionId = A_SESSION_ID, + inviteUserResult = { Result.failure(Throwable("Oops!")) }, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) room.givenRoomMembersState( MatrixRoomMembersState.Ready( persistentListOf( @@ -651,7 +750,6 @@ class MessagesPresenterTest { ) ) ) - room.givenInviteUserResult(Result.failure(Throwable("Oops!"))) val presenter = createMessagesPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -671,8 +769,19 @@ class MessagesPresenterTest { @Test fun `present - permission to post`() = runTest { - val matrixRoom = FakeMatrixRoom() - matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(true)) + val matrixRoom = FakeMatrixRoom( + canUserSendMessageResult = { _, messageEventType -> + when (messageEventType) { + MessageEventType.ROOM_MESSAGE -> Result.success(true) + MessageEventType.REACTION -> Result.success(true) + else -> lambdaError() + } + }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = matrixRoom) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -684,8 +793,19 @@ class MessagesPresenterTest { @Test fun `present - no permission to post`() = runTest { - val matrixRoom = FakeMatrixRoom() - matrixRoom.givenCanSendEventResult(MessageEventType.ROOM_MESSAGE, Result.success(false)) + val matrixRoom = FakeMatrixRoom( + canUserSendMessageResult = { _, messageEventType -> + when (messageEventType) { + MessageEventType.ROOM_MESSAGE -> Result.success(false) + MessageEventType.REACTION -> Result.success(false) + else -> lambdaError() + } + }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = matrixRoom) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -700,7 +820,13 @@ class MessagesPresenterTest { @Test fun `present - permission to redact own`() = runTest { - val matrixRoom = FakeMatrixRoom(canRedactOwn = true) + val matrixRoom = FakeMatrixRoom( + canRedactOwnResult = { Result.success(true) }, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOtherResult = { Result.success(false) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = matrixRoom) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -714,7 +840,13 @@ class MessagesPresenterTest { @Test fun `present - permission to redact other`() = runTest { - val matrixRoom = FakeMatrixRoom(canRedactOther = true) + val matrixRoom = FakeMatrixRoom( + canRedactOtherResult = { Result.success(true) }, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(false) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createMessagesPresenter(matrixRoom = matrixRoom) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -754,7 +886,13 @@ class MessagesPresenterTest { private fun TestScope.createMessagesPresenter( coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(), - matrixRoom: MatrixRoom = FakeMatrixRoom().apply { + matrixRoom: MatrixRoom = FakeMatrixRoom( + canUserSendMessageResult = { _, _ -> Result.success(true) }, + canRedactOwnResult = { Result.success(true) }, + canRedactOtherResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) }, + ).apply { givenRoomInfo(aRoomInfo(id = roomId, name = "")) }, navigator: FakeMessagesNavigator = FakeMessagesNavigator(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt index 0168fcbde5..e2fbeebe3a 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/attachments/AttachmentsPreviewPresenterTest.kt @@ -26,7 +26,9 @@ import com.google.common.truth.Truth.assertThat import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewEvents import io.element.android.features.messages.impl.attachments.preview.AttachmentsPreviewPresenter import io.element.android.features.messages.impl.attachments.preview.SendActionState +import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.api.MediaSender @@ -34,6 +36,7 @@ import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor import io.element.android.libraries.mediaviewer.api.local.LocalMedia import io.element.android.libraries.mediaviewer.test.viewer.aLocalMedia import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder import io.mockk.mockk import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.test.runTest @@ -49,13 +52,16 @@ class AttachmentsPreviewPresenterTest { @Test fun `present - send media success scenario`() = runTest { - val room = FakeMatrixRoom() - room.givenProgressCallbackValues( - listOf( + val sendMediaResult = lambdaRecorder> { + Result.success(FakeMediaUploadHandler()) + } + val room = FakeMatrixRoom( + progressCallbackValues = listOf( Pair(0, 10), Pair(5, 10), Pair(10, 10) - ) + ), + sendMediaResult = sendMediaResult, ) val presenter = createAttachmentsPreviewPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -70,15 +76,19 @@ class AttachmentsPreviewPresenterTest { assertThat(awaitItem().sendActionState).isEqualTo(SendActionState.Sending.Uploading(1f)) val successState = awaitItem() assertThat(successState.sendActionState).isEqualTo(SendActionState.Done) - assertThat(room.sendMediaCount).isEqualTo(1) + sendMediaResult.assertions().isCalledOnce() } } @Test fun `present - send media failure scenario`() = runTest { - val room = FakeMatrixRoom() val failure = MediaPreProcessor.Failure(null) - room.givenSendMediaResult(Result.failure(failure)) + val sendMediaResult = lambdaRecorder> { + Result.failure(failure) + } + val room = FakeMatrixRoom( + sendMediaResult = sendMediaResult, + ) val presenter = createAttachmentsPreviewPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -90,7 +100,7 @@ class AttachmentsPreviewPresenterTest { assertThat(loadingState.sendActionState).isEqualTo(SendActionState.Sending.Processing) val failureState = awaitItem() assertThat(failureState.sendActionState).isEqualTo(SendActionState.Failure(failure)) - assertThat(room.sendMediaCount).isEqualTo(0) + sendMediaResult.assertions().isCalledOnce() failureState.eventSink(AttachmentsPreviewEvents.ClearSendState) val clearedState = awaitItem() assertThat(clearedState.sendActionState).isEqualTo(SendActionState.Idle) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt index b5408434de..a15ea45a6c 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/report/ReportMessagePresenterTest.kt @@ -22,11 +22,14 @@ import app.cash.turbine.test import com.google.common.truth.Truth.assertThat import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher +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.MatrixRoom 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.room.FakeMatrixRoom import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -81,7 +84,12 @@ class ReportMessagePresenterTest { @Test fun `presenter - handle successful report and block user`() = runTest { - val room = FakeMatrixRoom() + val reportContentResult = lambdaRecorder> { _, _, _ -> + Result.success(Unit) + } + val room = FakeMatrixRoom( + reportContentResult = reportContentResult + ) val presenter = createReportMessagePresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -92,13 +100,18 @@ class ReportMessagePresenterTest { initialState.eventSink(ReportMessageEvents.Report) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java) - assertThat(room.reportedContentCount).isEqualTo(1) + reportContentResult.assertions().isCalledOnce() } } @Test fun `presenter - handle successful report`() = runTest { - val room = FakeMatrixRoom() + val reportContentResult = lambdaRecorder> { _, _, _ -> + Result.success(Unit) + } + val room = FakeMatrixRoom( + reportContentResult = reportContentResult + ) val presenter = createReportMessagePresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -107,15 +120,18 @@ class ReportMessagePresenterTest { initialState.eventSink(ReportMessageEvents.Report) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Success::class.java) - assertThat(room.reportedContentCount).isEqualTo(1) + reportContentResult.assertions().isCalledOnce() } } @Test fun `presenter - handle failed report`() = runTest { - val room = FakeMatrixRoom().apply { - givenReportContentResult(Result.failure(Exception("Failed to report content"))) + val reportContentResult = lambdaRecorder> { _, _, _ -> + Result.failure(Exception("Failed to report content")) } + val room = FakeMatrixRoom( + reportContentResult = reportContentResult + ) val presenter = createReportMessagePresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -125,7 +141,7 @@ class ReportMessagePresenterTest { assertThat(awaitItem().result).isInstanceOf(AsyncAction.Loading::class.java) val resultState = awaitItem() assertThat(resultState.result).isInstanceOf(AsyncAction.Failure::class.java) - assertThat(room.reportedContentCount).isEqualTo(1) + reportContentResult.assertions().isCalledOnce() resultState.eventSink(ReportMessageEvents.ClearError) assertThat(awaitItem().result).isInstanceOf(AsyncAction.Uninitialized::class.java) diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt index de48ee62b6..b944ea3d95 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/textcomposer/MessageComposerPresenterTest.kt @@ -43,6 +43,7 @@ import io.element.android.libraries.featureflag.api.FeatureFlagService import io.element.android.libraries.featureflag.api.FeatureFlags import io.element.android.libraries.featureflag.test.FakeFeatureFlagService import io.element.android.libraries.matrix.api.core.EventId +import io.element.android.libraries.matrix.api.core.ProgressCallback 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.ImageInfo @@ -67,6 +68,7 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.A_USER_ID_3 import io.element.android.libraries.matrix.test.A_USER_ID_4 import io.element.android.libraries.matrix.test.core.aBuildMeta +import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser import io.element.android.libraries.matrix.test.room.FakeMatrixRoom @@ -297,7 +299,13 @@ class MessageComposerPresenterTest { @Test fun `present - send message with rich text enabled`() = runTest { - val presenter = createPresenter(this) + val presenter = createPresenter( + coroutineScope = this, + room = FakeMatrixRoom( + sendMessageResult = { _, _, _ -> Result.success(Unit) }, + typingNoticeResult = { Result.success(Unit) } + ), + ) moleculeFlow(RecompositionMode.Immediate) { val state = presenter.present() remember(state, state.textEditorState.messageHtml()) { state } @@ -324,7 +332,14 @@ class MessageComposerPresenterTest { @Test fun `present - send message with plain text enabled`() = runTest { val permalinkBuilder = FakePermalinkBuilder(permalinkForUserLambda = { Result.success("") }) - val presenter = createPresenter(this, isRichTextEditorEnabled = false) + val presenter = createPresenter( + coroutineScope = this, + isRichTextEditorEnabled = false, + room = FakeMatrixRoom( + sendMessageResult = { _, _, _ -> Result.success(Unit) }, + typingNoticeResult = { Result.success(Unit) } + ), + ) moleculeFlow(RecompositionMode.Immediate) { val state = presenter.present() val messageMarkdown = state.textEditorState.messageMarkdown(permalinkBuilder) @@ -358,7 +373,10 @@ class MessageComposerPresenterTest { val timeline = FakeTimeline().apply { this.editMessageLambda = editMessageLambda } - val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline) + val fakeMatrixRoom = FakeMatrixRoom( + liveTimeline = timeline, + typingNoticeResult = { Result.success(Unit) } + ) val presenter = createPresenter( this, fakeMatrixRoom, @@ -407,7 +425,10 @@ class MessageComposerPresenterTest { val timeline = FakeTimeline().apply { this.editMessageLambda = editMessageLambda } - val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline) + val fakeMatrixRoom = FakeMatrixRoom( + liveTimeline = timeline, + typingNoticeResult = { Result.success(Unit) }, + ) val presenter = createPresenter( this, fakeMatrixRoom, @@ -456,7 +477,10 @@ class MessageComposerPresenterTest { val timeline = FakeTimeline().apply { this.replyMessageLambda = replyMessageLambda } - val fakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline) + val fakeMatrixRoom = FakeMatrixRoom( + liveTimeline = timeline, + typingNoticeResult = { Result.success(Unit) } + ) val presenter = createPresenter( this, fakeMatrixRoom, @@ -524,7 +548,9 @@ class MessageComposerPresenterTest { @Test fun `present - Pick image from gallery`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ) val presenter = createPresenter(this, room = room) pickerProvider.givenMimeType(MimeTypes.Images) mediaPreProcessor.givenResult( @@ -557,7 +583,9 @@ class MessageComposerPresenterTest { @Test fun `present - Pick video from gallery`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ) val presenter = createPresenter(this, room = room) pickerProvider.givenMimeType(MimeTypes.Videos) mediaPreProcessor.givenResult( @@ -607,13 +635,17 @@ class MessageComposerPresenterTest { @Test fun `present - Pick file from storage`() = runTest { - val room = FakeMatrixRoom() - room.givenProgressCallbackValues( - listOf( + val sendMediaResult = lambdaRecorder { _: ProgressCallback? -> + Result.success(FakeMediaUploadHandler()) + } + val room = FakeMatrixRoom( + progressCallbackValues = listOf( Pair(0, 10), Pair(5, 10), Pair(10, 10) - ) + ), + sendMediaResult = sendMediaResult, + typingNoticeResult = { Result.success(Unit) } ) val presenter = createPresenter(this, room = room) moleculeFlow(RecompositionMode.Immediate) { @@ -629,13 +661,15 @@ class MessageComposerPresenterTest { assertThat(awaitItem().attachmentsState).isEqualTo(AttachmentsState.Sending.Uploading(1f)) val sentState = awaitItem() assertThat(sentState.attachmentsState).isEqualTo(AttachmentsState.None) - assertThat(room.sendMediaCount).isEqualTo(1) + sendMediaResult.assertions().isCalledOnce() } } @Test fun `present - create poll`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ) val presenter = createPresenter(this, room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -652,7 +686,9 @@ class MessageComposerPresenterTest { @Test fun `present - share location`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ) val presenter = createPresenter(this, room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -669,7 +705,9 @@ class MessageComposerPresenterTest { @Test fun `present - Take photo`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ) val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() } val presenter = createPresenter( this, @@ -689,7 +727,9 @@ class MessageComposerPresenterTest { @Test fun `present - Take photo with permission request`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ) val permissionPresenter = FakePermissionsPresenter() val presenter = createPresenter( this, @@ -714,7 +754,9 @@ class MessageComposerPresenterTest { @Test fun `present - Record video`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ) val permissionPresenter = FakePermissionsPresenter().apply { setPermissionGranted() } val presenter = createPresenter( this, @@ -734,7 +776,9 @@ class MessageComposerPresenterTest { @Test fun `present - Record video with permission request`() = runTest { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ) val permissionPresenter = FakePermissionsPresenter() val presenter = createPresenter( this, @@ -759,9 +803,10 @@ class MessageComposerPresenterTest { @Test fun `present - Uploading media failure can be recovered from`() = runTest { - val room = FakeMatrixRoom().apply { - givenSendMediaResult(Result.failure(Exception())) - } + val room = FakeMatrixRoom( + sendMediaResult = { Result.failure(Exception()) }, + typingNoticeResult = { Result.success(Unit) } + ) val presenter = createPresenter(this, room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -842,15 +887,17 @@ class MessageComposerPresenterTest { val invitedUser = aRoomMember(userId = A_USER_ID_3, membership = RoomMembershipState.INVITE) val bob = aRoomMember(userId = A_USER_ID_2, membership = RoomMembershipState.JOIN) val david = aRoomMember(userId = A_USER_ID_4, displayName = "Dave", membership = RoomMembershipState.JOIN) + var canUserTriggerRoomNotificationResult = true val room = FakeMatrixRoom( isDirect = false, + canUserTriggerRoomNotificationResult = { Result.success(canUserTriggerRoomNotificationResult) }, + typingNoticeResult = { Result.success(Unit) } ).apply { givenRoomMembersState( MatrixRoomMembersState.Ready( persistentListOf(currentUser, invitedUser, bob, david), ) ) - givenCanTriggerRoomNotification(Result.success(true)) } val flagsService = FakeFeatureFlagService( mapOf( @@ -890,13 +937,10 @@ class MessageComposerPresenterTest { assertThat(awaitItem().memberSuggestions).isEmpty() // If user has no permission to send `@room` mentions, `RoomMemberSuggestion.Room` is not returned - room.givenCanTriggerRoomNotification(Result.success(false)) + canUserTriggerRoomNotificationResult = false initialState.eventSink(MessageComposerEvents.SuggestionReceived(Suggestion(0, 0, SuggestionType.Mention, ""))) assertThat(awaitItem().memberSuggestions) .containsExactly(ResolvedMentionSuggestion.Member(bob), ResolvedMentionSuggestion.Member(david)) - - // If room is a DM, `RoomMemberSuggestion.Room` is not returned - room.givenCanTriggerRoomNotification(Result.success(true)) } } @@ -910,13 +954,14 @@ class MessageComposerPresenterTest { isDirect = true, activeMemberCount = 2, isEncrypted = true, + canUserTriggerRoomNotificationResult = { Result.success(true) }, + typingNoticeResult = { Result.success(Unit) } ).apply { givenRoomMembersState( MatrixRoomMembersState.Ready( persistentListOf(currentUser, invitedUser, bob, david), ) ) - givenCanTriggerRoomNotification(Result.success(true)) } val flagsService = FakeFeatureFlagService( mapOf( @@ -973,7 +1018,14 @@ class MessageComposerPresenterTest { this.replyMessageLambda = replyMessageLambda this.editMessageLambda = editMessageLambda } - val room = FakeMatrixRoom(liveTimeline = timeline) + val sendMessageResult = lambdaRecorder { _: String, _: String?, _: List -> + Result.success(Unit) + } + val room = FakeMatrixRoom( + liveTimeline = timeline, + sendMessageResult = sendMessageResult, + typingNoticeResult = { Result.success(Unit) } + ) val presenter = createPresenter(room = room, coroutineScope = this) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -993,7 +1045,8 @@ class MessageComposerPresenterTest { advanceUntilIdle() - assertThat(room.sendMessageMentions).isEqualTo(listOf(Mention.User(A_USER_ID))) + sendMessageResult.assertions().isCalledOnce() + .with(value(A_MESSAGE), any(), value(listOf(Mention.User(A_USER_ID)))) // Check intentional mentions on reply sent initialState.eventSink(MessageComposerEvents.SetMode(aReplyMode())) @@ -1049,22 +1102,32 @@ class MessageComposerPresenterTest { @Test fun `present - handle typing notice event`() = runTest { - val room = FakeMatrixRoom() + val typingNoticeResult = lambdaRecorder> { Result.success(Unit) } + val room = FakeMatrixRoom( + typingNoticeResult = typingNoticeResult + ) val presenter = createPresenter(room = room, coroutineScope = this) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitFirstItem() - assertThat(room.typingRecord).isEmpty() + typingNoticeResult.assertions().isNeverCalled() initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true)) initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false)) - assertThat(room.typingRecord).isEqualTo(listOf(true, false)) + typingNoticeResult.assertions().isCalledExactly(2) + .withSequence( + listOf(value(true)), + listOf(value(false)), + ) } } @Test fun `present - handle typing notice event when sending typing notice is disabled`() = runTest { - val room = FakeMatrixRoom() + val typingNoticeResult = lambdaRecorder> { Result.success(Unit) } + val room = FakeMatrixRoom( + typingNoticeResult = typingNoticeResult + ) val store = InMemorySessionPreferencesStore( isSendTypingNotificationsEnabled = false ) @@ -1073,10 +1136,10 @@ class MessageComposerPresenterTest { presenter.present() }.test { val initialState = awaitFirstItem() - assertThat(room.typingRecord).isEmpty() + typingNoticeResult.assertions().isNeverCalled() initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(true)) initialState.eventSink.invoke(MessageComposerEvents.TypingNotice(false)) - assertThat(room.typingRecord).isEmpty() + typingNoticeResult.assertions().isNeverCalled() } } @@ -1215,7 +1278,10 @@ class MessageComposerPresenterTest { val timeline = FakeTimeline().apply { this.loadReplyDetailsLambda = loadReplyDetailsLambda } - val room = FakeMatrixRoom(liveTimeline = timeline) + val room = FakeMatrixRoom( + liveTimeline = timeline, + typingNoticeResult = { Result.success(Unit) }, + ) val permalinkBuilder = FakePermalinkBuilder() val presenter = createPresenter( room = room, @@ -1352,7 +1418,9 @@ class MessageComposerPresenterTest { private fun createPresenter( coroutineScope: CoroutineScope, - room: MatrixRoom = FakeMatrixRoom(), + room: MatrixRoom = FakeMatrixRoom( + typingNoticeResult = { Result.success(Unit) } + ), pickerProvider: PickerProvider = this.pickerProvider, featureFlagService: FeatureFlagService = this.featureFlagService, sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt index 58cf11c4a7..887ce88d95 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelineControllerTest.kt @@ -26,6 +26,7 @@ import io.element.android.libraries.matrix.test.A_UNIQUE_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.anEventTimelineItem +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.flowOf @@ -38,9 +39,9 @@ class TimelineControllerTest { val liveTimeline = FakeTimeline(name = "live") val detachedTimeline = FakeTimeline(name = "detached") val matrixRoom = FakeMatrixRoom( - liveTimeline = liveTimeline + liveTimeline = liveTimeline, + timelineFocusedOnEventResult = { Result.success(detachedTimeline) } ) - matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline)) val sut = TimelineController(matrixRoom) sut.activeTimelineFlow().test { @@ -68,8 +69,17 @@ class TimelineControllerTest { val liveTimeline = FakeTimeline(name = "live") val detachedTimeline1 = FakeTimeline(name = "detached 1") val detachedTimeline2 = FakeTimeline(name = "detached 2") + var callNumber = 0 val matrixRoom = FakeMatrixRoom( - liveTimeline = liveTimeline + liveTimeline = liveTimeline, + timelineFocusedOnEventResult = { + callNumber++ + when (callNumber) { + 1 -> Result.success(detachedTimeline1) + 2 -> Result.success(detachedTimeline2) + else -> lambdaError() + } + } ) val sut = TimelineController(matrixRoom) @@ -77,7 +87,6 @@ class TimelineControllerTest { awaitItem().also { state -> assertThat(state).isEqualTo(liveTimeline) } - matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline1)) sut.focusOnEvent(AN_EVENT_ID) awaitItem().also { state -> assertThat(state).isEqualTo(detachedTimeline1) @@ -85,7 +94,6 @@ class TimelineControllerTest { assertThat(detachedTimeline1.closeCounter).isEqualTo(0) assertThat(detachedTimeline2.closeCounter).isEqualTo(0) // Focus on another event should close the previous detached timeline - matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline2)) sut.focusOnEvent(AN_EVENT_ID) awaitItem().also { state -> assertThat(state).isEqualTo(detachedTimeline2) @@ -117,11 +125,10 @@ class TimelineControllerTest { val liveTimeline = FakeTimeline(name = "live") val detachedTimeline = FakeTimeline(name = "detached") val matrixRoom = FakeMatrixRoom( - liveTimeline = liveTimeline + liveTimeline = liveTimeline, + timelineFocusedOnEventResult = { Result.success(detachedTimeline) } ) - matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline)) val sut = TimelineController(matrixRoom) - sut.activeTimelineFlow().test { awaitItem().also { state -> assertThat(state).isEqualTo(liveTimeline) @@ -168,9 +175,9 @@ class TimelineControllerTest { sendMessageLambda = lambdaForDetached } val matrixRoom = FakeMatrixRoom( - liveTimeline = liveTimeline + liveTimeline = liveTimeline, + timelineFocusedOnEventResult = { Result.success(detachedTimeline) } ) - matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline)) val sut = TimelineController(matrixRoom) sut.activeTimelineFlow().test { sut.focusOnEvent(AN_EVENT_ID) @@ -193,9 +200,9 @@ class TimelineControllerTest { val liveTimeline = FakeTimeline(name = "live") val detachedTimeline = FakeTimeline(name = "detached") val matrixRoom = FakeMatrixRoom( - liveTimeline = liveTimeline + liveTimeline = liveTimeline, + timelineFocusedOnEventResult = { Result.success(detachedTimeline) } ) - matrixRoom.givenTimelineFocusedOnEventResult(Result.success(detachedTimeline)) val sut = TimelineController(matrixRoom) sut.activeTimelineFlow().test { diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt index ad52af11ca..5a171227e7 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/timeline/TimelinePresenterTest.kt @@ -133,7 +133,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2" ) ) ) - val room = FakeMatrixRoom(liveTimeline = timeline) + val room = FakeMatrixRoom( + liveTimeline = timeline, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + ) val sessionPreferencesStore = InMemorySessionPreferencesStore(isSendPublicReadReceiptsEnabled = false) val presenter = createTimelinePresenter( timeline = timeline, @@ -482,9 +485,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2" ) val room = FakeMatrixRoom( liveTimeline = liveTimeline, - ).apply { - givenTimelineFocusedOnEventResult(Result.success(detachedTimeline)) - } + timelineFocusedOnEventResult = { Result.success(detachedTimeline) }, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + ) val presenter = createTimelinePresenter( room = room, ) @@ -529,6 +532,7 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2" ) ) ), + canUserSendMessageResult = { _, _ -> Result.success(true) }, ), timelineItemIndexer = timelineItemIndexer, ) @@ -551,9 +555,9 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2" liveTimeline = FakeTimeline( timelineItems = flowOf(emptyList()), ), - ).apply { - givenTimelineFocusedOnEventResult(Result.failure(Throwable("An error"))) - }, + timelineFocusedOnEventResult = { Result.failure(Throwable("An error")) }, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + ) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -594,7 +598,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2" ) ) ) - val room = FakeMatrixRoom(liveTimeline = timeline).apply { + val room = FakeMatrixRoom( + liveTimeline = timeline, + canUserSendMessageResult = { _, _ -> Result.success(true) }, + ).apply { givenRoomMembersState(MatrixRoomMembersState.Unknown) } @@ -626,7 +633,10 @@ private const val FAKE_UNIQUE_ID_2 = "FAKE_UNIQUE_ID_2" private fun TestScope.createTimelinePresenter( timeline: Timeline = FakeTimeline(), - room: FakeMatrixRoom = FakeMatrixRoom(liveTimeline = timeline), + room: FakeMatrixRoom = FakeMatrixRoom( + liveTimeline = timeline, + canUserSendMessageResult = { _, _ -> Result.success(true) } + ), timelineItemsFactory: TimelineItemsFactory = aTimelineItemsFactory(), redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(), messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(), diff --git a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt index 1c08a76e3d..0e507bdecf 100644 --- a/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt +++ b/features/messages/impl/src/test/kotlin/io/element/android/features/messages/impl/voicemessages/composer/VoiceMessageComposerPresenterTest.kt @@ -28,7 +28,9 @@ import com.google.common.truth.Truth.assertThat import im.vector.app.features.analytics.plan.Composer import io.element.android.features.messages.impl.voicemessages.VoiceMessageException import io.element.android.features.messages.test.FakeMessageComposerContext +import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.test.AN_EVENT_ID +import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer @@ -45,6 +47,7 @@ import io.element.android.libraries.textcomposer.model.VoiceMessageState import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.collections.immutable.toImmutableList import kotlinx.collections.immutable.toPersistentList import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -63,7 +66,10 @@ class VoiceMessageComposerPresenterTest { recordingDuration = RECORDING_DURATION ) private val analyticsService = FakeAnalyticsService() - private val matrixRoom = FakeMatrixRoom() + private val sendMediaResult = lambdaRecorder> { Result.success(FakeMediaUploadHandler()) } + private val matrixRoom = FakeMatrixRoom( + sendMediaResult = sendMediaResult + ) private val mediaPreProcessor = FakeMediaPreProcessor().apply { givenAudioResult() } private val mediaSender = MediaSender(mediaPreProcessor, matrixRoom) private val messageComposerContext = FakeMessageComposerContext() @@ -295,7 +301,7 @@ class VoiceMessageComposerPresenterTest { val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - assertThat(matrixRoom.sendMediaCount).isEqualTo(1) + sendMediaResult.assertions().isCalledOnce() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) testPauseAndDestroy(finalState) @@ -346,7 +352,7 @@ class VoiceMessageComposerPresenterTest { val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - assertThat(matrixRoom.sendMediaCount).isEqualTo(1) + sendMediaResult.assertions().isCalledOnce() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) testPauseAndDestroy(finalState) @@ -369,7 +375,7 @@ class VoiceMessageComposerPresenterTest { val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - assertThat(matrixRoom.sendMediaCount).isEqualTo(1) + sendMediaResult.assertions().isCalledOnce() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) testPauseAndDestroy(finalState) @@ -393,7 +399,7 @@ class VoiceMessageComposerPresenterTest { val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(aPreviewState(isSending = true)) - assertThat(matrixRoom.sendMediaCount).isEqualTo(0) + sendMediaResult.assertions().isNeverCalled() assertThat(analyticsService.trackedErrors).hasSize(0) voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 0) @@ -418,13 +424,13 @@ class VoiceMessageComposerPresenterTest { ensureAllEventsConsumed() assertThat(previewState.voiceMessageState).isEqualTo(aPreviewState()) - assertThat(matrixRoom.sendMediaCount).isEqualTo(0) + sendMediaResult.assertions().isNeverCalled() mediaPreProcessor.givenAudioResult() previewState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) val finalState = awaitItem() assertThat(finalState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - assertThat(matrixRoom.sendMediaCount).isEqualTo(1) + sendMediaResult.assertions().isCalledOnce() voiceRecorder.assertCalls(started = 1, stopped = 1, deleted = 1) testPauseAndDestroy(finalState) @@ -461,7 +467,7 @@ class VoiceMessageComposerPresenterTest { assertThat(showSendFailureDialog).isFalse() } - assertThat(matrixRoom.sendMediaCount).isEqualTo(0) + sendMediaResult.assertions().isNeverCalled() testPauseAndDestroy(finalState) } } @@ -477,7 +483,7 @@ class VoiceMessageComposerPresenterTest { initialState.eventSink(VoiceMessageComposerEvents.SendVoiceMessage) assertThat(initialState.voiceMessageState).isEqualTo(VoiceMessageState.Idle) - assertThat(matrixRoom.sendMediaCount).isEqualTo(0) + sendMediaResult.assertions().isNeverCalled() assertThat(analyticsService.trackedErrors).hasSize(1) voiceRecorder.assertCalls(started = 0) @@ -496,7 +502,7 @@ class VoiceMessageComposerPresenterTest { val initialState = awaitItem() initialState.eventSink(VoiceMessageComposerEvents.RecorderEvent(VoiceMessageRecorderEvent.Start)) - assertThat(matrixRoom.sendMediaCount).isEqualTo(0) + sendMediaResult.assertions().isNeverCalled() assertThat(analyticsService.trackedErrors).containsExactly( VoiceMessageException.PermissionMissing(message = "Expected permission to record but none", cause = exception) ) diff --git a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt index 89536deaec..334ea7018b 100644 --- a/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt +++ b/features/poll/impl/src/test/kotlin/io/element/android/features/poll/impl/create/CreatePollPresenterTest.kt @@ -35,7 +35,6 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.timeline.item.event.PollContent import io.element.android.libraries.matrix.test.AN_EVENT_ID import io.element.android.libraries.matrix.test.room.FakeMatrixRoom -import io.element.android.libraries.matrix.test.room.SavePollInvocation import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.matrix.test.timeline.LiveTimelineProvider import io.element.android.services.analytics.test.FakeAnalyticsService @@ -51,9 +50,9 @@ import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test -@OptIn(ExperimentalCoroutinesApi::class) class CreatePollPresenterTest { - @get:Rule - val warmUpRule = WarmUpRule() +@OptIn(ExperimentalCoroutinesApi::class) +class CreatePollPresenterTest { + @get:Rule val warmUpRule = WarmUpRule() private val pollEventId = AN_EVENT_ID private var navUpInvocationsCount = 0 @@ -128,7 +127,13 @@ import org.junit.Test @Test fun `create poll sends a poll start event`() = runTest { - val presenter = createCreatePollPresenter(mode = CreatePollMode.NewPoll) + val createPollResult = lambdaRecorder, Int, PollKind, Result> { _, _, _, _ -> Result.success(Unit) } + val presenter = createCreatePollPresenter( + room = FakeMatrixRoom( + createPollResult = createPollResult + ), + mode = CreatePollMode.NewPoll, + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -139,15 +144,13 @@ import org.junit.Test skipItems(3) initial.eventSink(CreatePollEvents.Save) delay(1) // Wait for the coroutine to finish - assertThat(fakeMatrixRoom.createPollInvocations.size).isEqualTo(1) - assertThat(fakeMatrixRoom.createPollInvocations.last()).isEqualTo( - SavePollInvocation( - question = "A question?", - answers = listOf("Answer 1", "Answer 2"), - maxSelections = 1, - pollKind = PollKind.Disclosed + createPollResult.assertions().isCalledOnce() + .with( + value("A question?"), + value(listOf("Answer 1", "Answer 2")), + value(1), + value(PollKind.Disclosed), ) - ) assertThat(fakeAnalyticsService.capturedEvents.size).isEqualTo(2) assertThat(fakeAnalyticsService.capturedEvents[0]).isEqualTo( Composer( @@ -170,8 +173,15 @@ import org.junit.Test @Test fun `when poll creation fails, error is tracked`() = runTest { val error = Exception("cause") - fakeMatrixRoom.givenCreatePollResult(Result.failure(error)) - val presenter = createCreatePollPresenter(mode = CreatePollMode.NewPoll) + val createPollResult = lambdaRecorder, Int, PollKind, Result> { _, _, _, _ -> + Result.failure(error) + } + val presenter = createCreatePollPresenter( + room = FakeMatrixRoom( + createPollResult = createPollResult + ), + mode = CreatePollMode.NewPoll, + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -180,7 +190,7 @@ import org.junit.Test awaitItem().eventSink(CreatePollEvents.SetAnswer(1, "Answer 2")) awaitItem().eventSink(CreatePollEvents.Save) delay(1) // Wait for the coroutine to finish - assertThat(fakeMatrixRoom.createPollInvocations).hasSize(1) + createPollResult.assertions().isCalledOnce() assertThat(fakeAnalyticsService.capturedEvents).isEmpty() assertThat(fakeAnalyticsService.trackedErrors).hasSize(1) assertThat(fakeAnalyticsService.trackedErrors).containsExactly( @@ -252,14 +262,22 @@ import org.junit.Test @Test fun `when edit poll fails, error is tracked`() = runTest { val error = Exception("cause") + val editPollResult = lambdaRecorder { _: EventId, _: String, _: List, _: Int, _: PollKind -> + Result.failure(error) + } + val presenter = createCreatePollPresenter( + room = FakeMatrixRoom( + editPollResult = editPollResult, + liveTimeline = timeline, + ), + mode = CreatePollMode.EditPoll(pollEventId), + ) val editPollLambda = lambdaRecorder { _: EventId, _: String, _: List, _: Int, _: PollKind -> Result.failure(error) } timeline.apply { this.editPollLambda = editPollLambda } - fakeMatrixRoom.givenEditPollResult(Result.failure(error)) - val presenter = createCreatePollPresenter(mode = CreatePollMode.EditPoll(pollEventId)) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt index cffd2ae1f1..9fd89f482d 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/MatrixRoomFixture.kt @@ -17,11 +17,15 @@ package io.element.android.features.roomdetails 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.RoomMember +import io.element.android.libraries.matrix.api.room.StateEventType 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.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.room.aRoomInfo +import io.element.android.tests.testutils.lambda.lambdaError fun aMatrixRoom( roomId: RoomId = A_ROOM_ID, @@ -34,6 +38,16 @@ fun aMatrixRoom( isDirect: Boolean = false, notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(), emitRoomInfo: Boolean = false, + canInviteResult: (UserId) -> Result = { lambdaError() }, + canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() }, + userDisplayNameResult: () -> Result = { lambdaError() }, + userAvatarUrlResult: () -> Result = { lambdaError() }, + setNameResult: (String) -> Result = { lambdaError() }, + setTopicResult: (String) -> Result = { lambdaError() }, + updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() }, + removeAvatarResult: () -> Result = { lambdaError() }, + canUserJoinCallResult: (UserId) -> Result = { lambdaError() }, + getUpdatedMemberResult: (UserId) -> Result = { lambdaError() }, ) = FakeMatrixRoom( roomId = roomId, displayName = displayName, @@ -42,7 +56,17 @@ fun aMatrixRoom( isEncrypted = isEncrypted, isPublic = isPublic, isDirect = isDirect, - notificationSettingsService = notificationSettingsService + notificationSettingsService = notificationSettingsService, + canInviteResult = canInviteResult, + canSendStateResult = canSendStateResult, + userDisplayNameResult = userDisplayNameResult, + userAvatarUrlResult = userAvatarUrlResult, + setNameResult = setNameResult, + setTopicResult = setTopicResult, + updateAvatarResult = updateAvatarResult, + removeAvatarResult = removeAvatarResult, + canUserJoinCallResult = canUserJoinCallResult, + getUpdatedMemberResult = getUpdatedMemberResult, ).apply { if (emitRoomInfo) { givenRoomInfo( diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt index 92ac27454a..ff89aba9be 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTest.kt @@ -53,6 +53,9 @@ import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.FakeLifecycleOwner import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate +import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import io.element.android.tests.testutils.testCoroutineDispatchers import io.element.android.tests.testutils.withFakeLifecycleOwner import kotlinx.collections.immutable.persistentListOf @@ -110,7 +113,11 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state is created from room if roomInfo is null`() = runTest { - val room = aMatrixRoom() + val room = aMatrixRoom( + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room) presenter.test { val initialState = awaitItem() @@ -128,7 +135,11 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state is updated with roomInfo if it exists`() = runTest { val roomInfo = aRoomInfo(name = "A room name", topic = "A topic", avatarUrl = "https://matrix.org/avatar.jpg") - val room = aMatrixRoom().apply { + val room = aMatrixRoom( + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ).apply { givenRoomInfo(roomInfo) } val presenter = createRoomDetailsPresenter(room) @@ -145,7 +156,12 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state with no room name`() = runTest { - val room = aMatrixRoom(displayName = "") + val room = aMatrixRoom( + displayName = "", + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room) presenter.test { val initialState = awaitItem() @@ -162,6 +178,16 @@ class RoomDetailsPresenterTest { val room = aMatrixRoom( isEncrypted = true, isDirect = true, + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + getUpdatedMemberResult = { userId -> + when (userId) { + A_SESSION_ID -> Result.success(myRoomMember) + A_USER_ID_2 -> Result.success(otherRoomMember) + else -> lambdaError() + } + }, ).apply { val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) @@ -181,9 +207,11 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can invite others to room`() = runTest { - val room = aMatrixRoom().apply { - givenCanInviteResult(Result.success(true)) - } + val room = aMatrixRoom( + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) presenter.test { // Initially false @@ -197,9 +225,11 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can not invite others to room`() = runTest { - val room = aMatrixRoom().apply { - givenCanInviteResult(Result.success(false)) - } + val room = aMatrixRoom( + canInviteResult = { Result.success(false) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room) presenter.test { assertThat(awaitItem().canInvite).isFalse() @@ -210,9 +240,11 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when canInvite errors`() = runTest { - val room = aMatrixRoom().apply { - givenCanInviteResult(Result.failure(Throwable("Whoops"))) - } + val room = aMatrixRoom( + canInviteResult = { Result.failure(Throwable("Whoops")) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room) presenter.test { assertThat(awaitItem().canInvite).isFalse() @@ -223,12 +255,18 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can edit one attribute`() = runTest { - val room = aMatrixRoom().apply { - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) - givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false)) - givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Whelp"))) - givenCanInviteResult(Result.success(false)) - } + val room = aMatrixRoom( + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_TOPIC -> Result.success(true) + StateEventType.ROOM_NAME -> Result.success(false) + StateEventType.ROOM_AVATAR -> Result.failure(Throwable("Whelp")) + else -> lambdaError() + } + }, + canInviteResult = { Result.success(false) }, + canUserJoinCallResult = { Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room) presenter.test { // Initially false @@ -247,14 +285,26 @@ class RoomDetailsPresenterTest { val room = aMatrixRoom( isEncrypted = true, isDirect = true, + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_TOPIC -> Result.success(true) + StateEventType.ROOM_NAME -> Result.success(true) + StateEventType.ROOM_AVATAR -> Result.success(true) + else -> lambdaError() + } + }, + canInviteResult = { Result.success(false) }, + canUserJoinCallResult = { Result.success(true) }, + getUpdatedMemberResult = { userId -> + when (userId) { + A_SESSION_ID -> Result.success(myRoomMember) + A_USER_ID_2 -> Result.success(otherRoomMember) + else -> lambdaError() + } + }, ).apply { val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) - - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) - givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true)) - givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true)) - givenCanInviteResult(Result.success(false)) } val presenter = createRoomDetailsPresenter(room) presenter.test { @@ -278,12 +328,28 @@ class RoomDetailsPresenterTest { isEncrypted = true, isDirect = true, topic = null, + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_AVATAR, + StateEventType.ROOM_TOPIC, + StateEventType.ROOM_NAME -> Result.success(true) + else -> lambdaError() + } + }, + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + getUpdatedMemberResult = { userId -> + when (userId) { + A_SESSION_ID -> Result.success(myRoomMember) + A_USER_ID_2 -> Result.success(otherRoomMember) + else -> lambdaError() + } + }, ).apply { val roomMembers = persistentListOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) - - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) } + val presenter = createRoomDetailsPresenter(room) presenter.test { skipItems(1) @@ -297,12 +363,20 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can edit all attributes`() = runTest { - val room = aMatrixRoom().apply { - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) - givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true)) - givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true)) - givenCanInviteResult(Result.success(false)) - } + val room = aMatrixRoom( + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_TOPIC -> Result.success(true) + StateEventType.ROOM_NAME -> Result.success(true) + StateEventType.ROOM_AVATAR -> Result.success(true) + else -> lambdaError() + } + }, + canInviteResult = { + Result.success(false) + }, + canUserJoinCallResult = { Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room) presenter.test { // Initially false @@ -316,12 +390,20 @@ class RoomDetailsPresenterTest { @Test fun `present - initial state when user can edit no attributes`() = runTest { - val room = aMatrixRoom().apply { - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(false)) - givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false)) - givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false)) - givenCanInviteResult(Result.success(false)) - } + val room = aMatrixRoom( + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_TOPIC -> Result.success(false) + StateEventType.ROOM_NAME -> Result.success(false) + StateEventType.ROOM_AVATAR -> Result.success(false) + else -> lambdaError() + } + }, + canInviteResult = { + Result.success(false) + }, + canUserJoinCallResult = { Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room) presenter.test { // Initially false, and no further events @@ -333,11 +415,21 @@ class RoomDetailsPresenterTest { @Test fun `present - topic state is hidden when no topic and user has no permission`() = runTest { - val room = aMatrixRoom(topic = null).apply { - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(false)) - givenCanInviteResult(Result.success(false)) - } - + val room = aMatrixRoom( + topic = null, + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_AVATAR, + StateEventType.ROOM_NAME -> Result.success(true) + StateEventType.ROOM_TOPIC -> Result.success(false) + else -> lambdaError() + } + }, + canInviteResult = { + Result.success(false) + }, + canUserJoinCallResult = { Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room) presenter.test { // The initial state is "hidden" and no further state changes happen @@ -349,12 +441,23 @@ class RoomDetailsPresenterTest { @Test fun `present - topic state is 'can add topic' when no topic and user has permission`() = runTest { - val room = aMatrixRoom(topic = null).apply { - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) - givenCanInviteResult(Result.success(false)) + val room = aMatrixRoom( + topic = null, + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_AVATAR, + StateEventType.ROOM_TOPIC, + StateEventType.ROOM_NAME -> Result.success(true) + else -> lambdaError() + } + }, + canInviteResult = { + Result.success(false) + }, + canUserJoinCallResult = { Result.success(true) }, + ).apply { givenRoomInfo(aRoomInfo(topic = null)) } - val presenter = createRoomDetailsPresenter(room) presenter.test { // Ignore the initial state @@ -370,7 +473,11 @@ class RoomDetailsPresenterTest { @Test fun `present - leave room event is passed on to leave room presenter`() = runTest { val leaveRoomPresenter = FakeLeaveRoomPresenter() - val room = aMatrixRoom() + val room = aMatrixRoom( + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val presenter = createRoomDetailsPresenter( room = room, leaveRoomPresenter = leaveRoomPresenter, @@ -379,7 +486,11 @@ class RoomDetailsPresenterTest { presenter.test { awaitItem().eventSink(RoomDetailsEvent.LeaveRoom) - assertThat(leaveRoomPresenter.events).contains(LeaveRoomEvent.ShowConfirmation(room.roomId)) + assertThat(leaveRoomPresenter.events).contains( + LeaveRoomEvent.ShowConfirmation( + room.roomId + ) + ) cancelAndIgnoreRemainingEvents() } @@ -389,33 +500,54 @@ class RoomDetailsPresenterTest { fun `present - notification mode changes`() = runTest { val leaveRoomPresenter = FakeLeaveRoomPresenter() val notificationSettingsService = FakeNotificationSettingsService() - val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) + val room = aMatrixRoom( + notificationSettingsService = notificationSettingsService, + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val presenter = createRoomDetailsPresenter( room = room, leaveRoomPresenter = leaveRoomPresenter, notificationSettingsService = notificationSettingsService, ) presenter.test { - notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + notificationSettingsService.setRoomNotificationMode( + room.roomId, + RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + ) val updatedState = consumeItemsUntilPredicate { it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() - assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo( + RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + ) cancelAndIgnoreRemainingEvents() } } @Test fun `present - mute room notifications`() = runTest { - val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) - val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) - val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService) + val notificationSettingsService = + FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) + val room = aMatrixRoom( + notificationSettingsService = notificationSettingsService, + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) + val presenter = createRoomDetailsPresenter( + room = room, + notificationSettingsService = notificationSettingsService + ) presenter.test { awaitItem().eventSink(RoomDetailsEvent.MuteNotification) val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE }.last() - assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MUTE) + assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo( + RoomNotificationMode.MUTE + ) cancelAndIgnoreRemainingEvents() } } @@ -426,29 +558,50 @@ class RoomDetailsPresenterTest { initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES ) - val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) - val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService) + val room = aMatrixRoom( + notificationSettingsService = notificationSettingsService, + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) + val presenter = createRoomDetailsPresenter( + room = room, + notificationSettingsService = notificationSettingsService + ) presenter.test { awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification) val updatedState = consumeItemsUntilPredicate { it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES }.last() - assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES) + assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo( + RoomNotificationMode.ALL_MESSAGES + ) cancelAndIgnoreRemainingEvents() } } @Test fun `present - when set is favorite event is emitted, then the action is called`() = runTest { - val room = FakeMatrixRoom() + val setIsFavoriteResult = lambdaRecorder> { _ -> Result.success(Unit) } + val room = FakeMatrixRoom( + setIsFavoriteResult = setIsFavoriteResult, + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val analyticsService = FakeAnalyticsService() - val presenter = createRoomDetailsPresenter(room = room, analyticsService = analyticsService) + val presenter = + createRoomDetailsPresenter(room = room, analyticsService = analyticsService) presenter.test { val initialState = awaitItem() initialState.eventSink(RoomDetailsEvent.SetFavorite(true)) - assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true)) + setIsFavoriteResult.assertions().isCalledOnce().with(value(true)) initialState.eventSink(RoomDetailsEvent.SetFavorite(false)) - assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false)) + setIsFavoriteResult.assertions().isCalledExactly(2) + .withSequence( + listOf(value(true)), + listOf(value(false)), + ) assertThat(analyticsService.capturedEvents).containsExactly( Interaction(name = Interaction.Name.MobileRoomFavouriteToggle), Interaction(name = Interaction.Name.MobileRoomFavouriteToggle) @@ -459,7 +612,11 @@ class RoomDetailsPresenterTest { @Test fun `present - changes in room info updates the is favorite flag`() = runTest { - val room = aMatrixRoom() + val room = aMatrixRoom( + canInviteResult = { Result.success(true) }, + canUserJoinCallResult = { Result.success(true) }, + canSendStateResult = { _, _ -> Result.success(true) }, + ) val presenter = createRoomDetailsPresenter(room = room) presenter.test { room.givenRoomInfo(aRoomInfo(isFavorite = true)) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt index f287fe4ab4..5d8a43ca4b 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/edit/RoomDetailsEditPresenterTest.kt @@ -39,6 +39,9 @@ import io.element.android.libraries.permissions.api.PermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenter import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory import io.element.android.tests.testutils.WarmUpRule +import io.element.android.tests.testutils.lambda.lambdaError +import io.element.android.tests.testutils.lambda.lambdaRecorder +import io.element.android.tests.testutils.lambda.value import io.mockk.every import io.mockk.mockk import io.mockk.mockkStatic @@ -98,6 +101,7 @@ class RoomDetailsEditPresenterTest { displayName = A_ROOM_NAME, rawName = A_ROOM_RAW_NAME, emitRoomInfo = true, + canSendStateResult = { _, _ -> Result.success(true) } ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { @@ -120,11 +124,17 @@ class RoomDetailsEditPresenterTest { @Test fun `present - sets canChangeName if user has permission`() = runTest { - val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply { - givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(true)) - givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false)) - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops"))) - } + val room = aMatrixRoom( + avatarUrl = AN_AVATAR_URL, + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_NAME -> Result.success(true) + StateEventType.ROOM_AVATAR -> Result.success(false) + StateEventType.ROOM_TOPIC -> Result.failure(Throwable("Oops")) + else -> lambdaError() + } + }, + ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -144,11 +154,17 @@ class RoomDetailsEditPresenterTest { @Test fun `present - sets canChangeAvatar if user has permission`() = runTest { - val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply { - givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false)) - givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true)) - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.failure(Throwable("Oops"))) - } + val room = aMatrixRoom( + avatarUrl = AN_AVATAR_URL, + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_NAME -> Result.success(false) + StateEventType.ROOM_AVATAR -> Result.success(true) + StateEventType.ROOM_TOPIC -> Result.failure(Throwable("Oops")) + else -> lambdaError() + } + } + ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -168,11 +184,17 @@ class RoomDetailsEditPresenterTest { @Test fun `present - sets canChangeTopic if user has permission`() = runTest { - val room = aMatrixRoom(avatarUrl = AN_AVATAR_URL).apply { - givenCanSendStateResult(StateEventType.ROOM_NAME, Result.success(false)) - givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Oops"))) - givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) - } + val room = aMatrixRoom( + avatarUrl = AN_AVATAR_URL, + canSendStateResult = { _, stateEventType -> + when (stateEventType) { + StateEventType.ROOM_NAME -> Result.success(false) + StateEventType.ROOM_AVATAR -> Result.failure(Throwable("Oops")) + StateEventType.ROOM_TOPIC -> Result.success(true) + else -> lambdaError() + } + } + ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -197,6 +219,7 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, + canSendStateResult = { _, _ -> Result.success(true) } ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { @@ -240,6 +263,7 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, + canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(anotherAvatarUri) val presenter = createRoomDetailsEditPresenter(room) @@ -262,6 +286,7 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, + canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(anotherAvatarUri) val fakePermissionsPresenter = FakePermissionsPresenter() @@ -298,6 +323,7 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, + canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(roomAvatarUri) val presenter = createRoomDetailsEditPresenter(room) @@ -346,6 +372,7 @@ class RoomDetailsEditPresenterTest { displayName = "fallback", avatarUrl = null, emitRoomInfo = true, + canSendStateResult = { _, _ -> Result.success(true) } ) fakePickerProvider.givenResult(roomAvatarUri) val presenter = createRoomDetailsEditPresenter(room) @@ -389,11 +416,18 @@ class RoomDetailsEditPresenterTest { @Test fun `present - save changes room details if different`() = runTest { + val setNameResult = lambdaRecorder { _: String -> Result.success(Unit) } + val setTopicResult = lambdaRecorder { _: String -> Result.success(Unit) } + val removeAvatarResult = lambdaRecorder> { Result.success(Unit) } val room = aMatrixRoom( topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, + setNameResult = setNameResult, + setTopicResult = setTopicResult, + removeAvatarResult = removeAvatarResult, + canSendStateResult = { _, _ -> Result.success(true) } ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { @@ -405,16 +439,20 @@ class RoomDetailsEditPresenterTest { initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) initialState.eventSink(RoomDetailsEditEvents.Save) skipItems(5) - assertThat(room.newName).isEqualTo("New name") - assertThat(room.newTopic).isEqualTo("New topic") - assertThat(room.newAvatarData).isNull() - assertThat(room.removedAvatar).isTrue() + setNameResult.assertions().isCalledOnce().with(value("New name")) + setTopicResult.assertions().isCalledOnce().with(value("New topic")) + removeAvatarResult.assertions().isCalledOnce() } } @Test fun `present - save doesn't change room details if they're the same trimmed`() = runTest { - val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL) + val room = aMatrixRoom( + topic = "My topic", + displayName = "Name", + avatarUrl = AN_AVATAR_URL, + canSendStateResult = { _, _ -> Result.success(true) } + ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -423,17 +461,18 @@ class RoomDetailsEditPresenterTest { initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName(" Name ")) initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic(" My topic ")) initialState.eventSink(RoomDetailsEditEvents.Save) - assertThat(room.newName).isNull() - assertThat(room.newTopic).isNull() - assertThat(room.newAvatarData).isNull() - assertThat(room.removedAvatar).isFalse() cancelAndIgnoreRemainingEvents() } } @Test fun `present - save doesn't change topic if it was unset and is now blank`() = runTest { - val room = aMatrixRoom(topic = null, displayName = "Name", avatarUrl = AN_AVATAR_URL) + val room = aMatrixRoom( + topic = null, + displayName = "Name", + avatarUrl = AN_AVATAR_URL, + canSendStateResult = { _, _ -> Result.success(true) } + ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -441,17 +480,18 @@ class RoomDetailsEditPresenterTest { val initialState = awaitItem() initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("")) initialState.eventSink(RoomDetailsEditEvents.Save) - assertThat(room.newName).isNull() - assertThat(room.newTopic).isNull() - assertThat(room.newAvatarData).isNull() - assertThat(room.removedAvatar).isFalse() cancelAndIgnoreRemainingEvents() } } @Test fun `present - save doesn't change name if it's now empty`() = runTest { - val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL) + val room = aMatrixRoom( + topic = "My topic", + displayName = "Name", + avatarUrl = AN_AVATAR_URL, + canSendStateResult = { _, _ -> Result.success(true) } + ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -459,17 +499,20 @@ class RoomDetailsEditPresenterTest { val initialState = awaitItem() initialState.eventSink(RoomDetailsEditEvents.UpdateRoomName("")) initialState.eventSink(RoomDetailsEditEvents.Save) - assertThat(room.newName).isNull() - assertThat(room.newTopic).isNull() - assertThat(room.newAvatarData).isNull() - assertThat(room.removedAvatar).isFalse() cancelAndIgnoreRemainingEvents() } } @Test fun `present - save processes and sets avatar when processor returns successfully`() = runTest { - val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL) + val updateAvatarResult = lambdaRecorder { _: String, _: ByteArray -> Result.success(Unit) } + val room = aMatrixRoom( + topic = "My topic", + displayName = "Name", + avatarUrl = AN_AVATAR_URL, + updateAvatarResult = updateAvatarResult, + canSendStateResult = { _, _ -> Result.success(true) } + ) givenPickerReturnsFile() val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { @@ -478,17 +521,19 @@ class RoomDetailsEditPresenterTest { val initialState = awaitItem() initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) initialState.eventSink(RoomDetailsEditEvents.Save) - skipItems(3) - assertThat(room.newName).isNull() - assertThat(room.newTopic).isNull() - assertThat(room.newAvatarData).isSameInstanceAs(fakeFileContents) - assertThat(room.removedAvatar).isFalse() + skipItems(4) + updateAvatarResult.assertions().isCalledOnce().with(value("image/jpeg"), value(fakeFileContents)) } } @Test fun `present - save does not set avatar data if processor fails`() = runTest { - val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL) + val room = aMatrixRoom( + topic = "My topic", + displayName = "Name", + avatarUrl = AN_AVATAR_URL, + canSendStateResult = { _, _ -> Result.success(true) } + ) fakePickerProvider.givenResult(anotherAvatarUri) fakeMediaPreProcessor.givenResult(Result.failure(Throwable("Oh no"))) val presenter = createRoomDetailsEditPresenter(room) @@ -498,11 +543,7 @@ class RoomDetailsEditPresenterTest { val initialState = awaitItem() initialState.eventSink(RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) initialState.eventSink(RoomDetailsEditEvents.Save) - skipItems(2) - assertThat(room.newName).isNull() - assertThat(room.newTopic).isNull() - assertThat(room.newAvatarData).isNull() - assertThat(room.removedAvatar).isFalse() + skipItems(3) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) } } @@ -514,9 +555,9 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, - ).apply { - givenSetNameResult(Result.failure(Throwable("!"))) - } + setNameResult = { Result.failure(Throwable("!")) }, + canSendStateResult = { _, _ -> Result.success(true) } + ) saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomName("New name")) } @@ -527,9 +568,9 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, - ).apply { - givenSetTopicResult(Result.failure(Throwable("!"))) - } + setTopicResult = { Result.failure(Throwable("!")) }, + canSendStateResult = { _, _ -> Result.success(true) } + ) saveAndAssertFailure(room, RoomDetailsEditEvents.UpdateRoomTopic("New topic")) } @@ -540,9 +581,9 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, - ).apply { - givenRemoveAvatarResult(Result.failure(Throwable("!"))) - } + removeAvatarResult = { Result.failure(Throwable("!")) }, + canSendStateResult = { _, _ -> Result.success(true) } + ) saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.Remove)) } @@ -554,18 +595,22 @@ class RoomDetailsEditPresenterTest { displayName = "Name", avatarUrl = AN_AVATAR_URL, emitRoomInfo = true, - ).apply { - givenUpdateAvatarResult(Result.failure(Throwable("!"))) - } + updateAvatarResult = { _, _ -> Result.failure(Throwable("!")) }, + canSendStateResult = { _, _ -> Result.success(true) } + ) saveAndAssertFailure(room, RoomDetailsEditEvents.HandleAvatarAction(AvatarAction.ChoosePhoto)) } @Test fun `present - CancelSaveChanges resets save action state`() = runTest { givenPickerReturnsFile() - val room = aMatrixRoom(topic = "My topic", displayName = "Name", avatarUrl = AN_AVATAR_URL).apply { - givenSetTopicResult(Result.failure(Throwable("!"))) - } + val room = aMatrixRoom( + topic = "My topic", + displayName = "Name", + avatarUrl = AN_AVATAR_URL, + setTopicResult = { Result.failure(Throwable("!")) }, + canSendStateResult = { _, _ -> Result.success(true) } + ) val presenter = createRoomDetailsEditPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -573,7 +618,7 @@ class RoomDetailsEditPresenterTest { val initialState = awaitItem() initialState.eventSink(RoomDetailsEditEvents.UpdateRoomTopic("foo")) initialState.eventSink(RoomDetailsEditEvents.Save) - skipItems(2) + skipItems(3) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Failure::class.java) initialState.eventSink(RoomDetailsEditEvents.CancelSaveChanges) assertThat(awaitItem().saveAction).isInstanceOf(AsyncAction.Uninitialized::class.java) diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt index a352d8bf27..8ca8b7b810 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/RoomMemberListPresenterTest.kt @@ -52,7 +52,10 @@ class RoomMemberListPresenterTest { @Test fun `member loading is done automatically on start, but is async`() = runTest { - val room = FakeMatrixRoom().apply { + val room = FakeMatrixRoom( + updateMembersResult = { Result.success(Unit) }, + canInviteResult = { Result.success(true) } + ).apply { // Needed to avoid discarding the loaded members as a partial and invalid result givenRoomInfo(aRoomInfo(joinedMembersCount = 2)) } @@ -78,7 +81,12 @@ class RoomMemberListPresenterTest { @Test fun `open search`() = runTest { - val presenter = createPresenter() + val presenter = createPresenter( + matrixRoom = FakeMatrixRoom( + updateMembersResult = { Result.success(Unit) }, + canInviteResult = { Result.success(true) } + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -93,7 +101,12 @@ class RoomMemberListPresenterTest { @Test fun `search for something which is not found`() = runTest { - val presenter = createPresenter() + val presenter = createPresenter( + matrixRoom = FakeMatrixRoom( + updateMembersResult = { Result.success(Unit) }, + canInviteResult = { Result.success(true) } + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -112,7 +125,12 @@ class RoomMemberListPresenterTest { @Test fun `search for something which is found`() = runTest { - val presenter = createPresenter() + val presenter = createPresenter( + matrixRoom = FakeMatrixRoom( + updateMembersResult = { Result.success(Unit) }, + canInviteResult = { Result.success(true) } + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -134,9 +152,10 @@ class RoomMemberListPresenterTest { @Test fun `present - asynchronously sets canInvite when user has correct power level`() = runTest { val presenter = createPresenter( - matrixRoom = FakeMatrixRoom().apply { - givenCanInviteResult(Result.success(true)) - } + matrixRoom = FakeMatrixRoom( + canInviteResult = { Result.success(true) }, + updateMembersResult = { Result.success(Unit) } + ) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -150,9 +169,10 @@ class RoomMemberListPresenterTest { @Test fun `present - asynchronously sets canInvite when user does not have correct power level`() = runTest { val presenter = createPresenter( - matrixRoom = FakeMatrixRoom().apply { - givenCanInviteResult(Result.success(false)) - } + matrixRoom = FakeMatrixRoom( + canInviteResult = { Result.success(false) }, + updateMembersResult = { Result.success(Unit) } + ) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -166,9 +186,10 @@ class RoomMemberListPresenterTest { @Test fun `present - asynchronously sets canInvite when power level check fails`() = runTest { val presenter = createPresenter( - matrixRoom = FakeMatrixRoom().apply { - givenCanInviteResult(Result.failure(Throwable("Eek"))) - } + matrixRoom = FakeMatrixRoom( + canInviteResult = { Result.failure(Throwable("Eek")) }, + updateMembersResult = { Result.success(Unit) } + ) ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -183,7 +204,14 @@ class RoomMemberListPresenterTest { fun `present - RoomMemberSelected by default opens the room member details through the navigator`() = runTest { val navigator = FakeRoomMemberListNavigator() val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = false) - val presenter = createPresenter(moderationPresenter = moderationPresenter, navigator = navigator) + val presenter = createPresenter( + moderationPresenter = moderationPresenter, + navigator = navigator, + matrixRoom = FakeMatrixRoom( + updateMembersResult = { Result.success(Unit) }, + canInviteResult = { Result.success(true) } + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -205,7 +233,14 @@ class RoomMemberListPresenterTest { val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = true).apply { givenState(capturingState) } - val presenter = createPresenter(moderationPresenter = moderationPresenter, navigator = navigator) + val presenter = createPresenter( + moderationPresenter = moderationPresenter, + navigator = navigator, + matrixRoom = FakeMatrixRoom( + updateMembersResult = { Result.success(Unit) }, + canInviteResult = { Result.success(true) } + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -236,10 +271,12 @@ private fun TestScope.createDataSource( @ExperimentalCoroutinesApi private fun TestScope.createPresenter( coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true), - matrixRoom: MatrixRoom = FakeMatrixRoom(), + matrixRoom: MatrixRoom = FakeMatrixRoom( + updateMembersResult = { Result.success(Unit) } + ), roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers), moderationPresenter: FakeRoomMembersModerationPresenter = FakeRoomMembersModerationPresenter(), - navigator: RoomMemberListNavigator = object : RoomMemberListNavigator { } + navigator: RoomMemberListNavigator = object : RoomMemberListNavigator {} ) = RoomMemberListPresenter( room = matrixRoom, roomMemberListDataSource = roomMemberListDataSource, diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt index c9df8d381c..1012de62bd 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTest.kt @@ -34,6 +34,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.UserId import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState +import io.element.android.libraries.matrix.test.AN_EXCEPTION import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.A_THROWABLE import io.element.android.libraries.matrix.test.FakeMatrixClient @@ -53,9 +54,11 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - returns the room member's data, then updates it if needed`() = runTest { val roomMember = aRoomMember(displayName = "Alice") - val room = aMatrixRoom().apply { - givenUserDisplayNameResult(Result.success("A custom name")) - givenUserAvatarUrlResult(Result.success("A custom avatar")) + val room = aMatrixRoom( + userDisplayNameResult = { Result.success("A custom name") }, + userAvatarUrlResult = { Result.success("A custom avatar") }, + getUpdatedMemberResult = { Result.success(roomMember) }, + ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember))) } val presenter = createRoomMemberDetailsPresenter( @@ -82,11 +85,14 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - will recover when retrieving room member details fails`() = runTest { val roomMember = aRoomMember(displayName = "Alice") - val room = aMatrixRoom().apply { - givenUserDisplayNameResult(Result.failure(Throwable())) - givenUserAvatarUrlResult(Result.failure(Throwable())) + val room = aMatrixRoom( + userDisplayNameResult = { Result.failure(Throwable()) }, + userAvatarUrlResult = { Result.failure(Throwable()) }, + getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) }, + ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember))) } + val presenter = createRoomMemberDetailsPresenter( room = room, roomMemberId = roomMember.userId @@ -105,9 +111,11 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - will fallback to original data if the updated data is null`() = runTest { val roomMember = aRoomMember(displayName = "Alice") - val room = aMatrixRoom().apply { - givenUserDisplayNameResult(Result.success(null)) - givenUserAvatarUrlResult(Result.success(null)) + val room = aMatrixRoom( + userDisplayNameResult = { Result.success(null) }, + userAvatarUrlResult = { Result.success(null) }, + getUpdatedMemberResult = { Result.success(roomMember) } + ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(roomMember))) } val presenter = createRoomMemberDetailsPresenter( @@ -128,10 +136,11 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - will fallback to user profile if user is not a member of the room`() = runTest { val bobProfile = aMatrixUser("@bob:server.org", "Bob", avatarUrl = "anAvatarUrl") - val room = aMatrixRoom().apply { - givenUserDisplayNameResult(Result.failure(Exception("Not a member!"))) - givenUserAvatarUrlResult(Result.failure(Exception("Not a member!"))) - } + val room = aMatrixRoom( + userDisplayNameResult = { Result.failure(Exception("Not a member!")) }, + userAvatarUrlResult = { Result.failure(Exception("Not a member!")) }, + getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) }, + ) val client = FakeMatrixClient().apply { givenGetProfileResult(bobProfile.userId, Result.success(bobProfile)) } @@ -154,7 +163,13 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest { - val presenter = createRoomMemberDetailsPresenter() + val presenter = createRoomMemberDetailsPresenter( + room = aMatrixRoom( + getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) }, + userDisplayNameResult = { Result.success("Alice") }, + userAvatarUrlResult = { Result.success("anAvatarUrl") }, + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -176,6 +191,11 @@ class RoomMemberDetailsPresenterTest { val client = FakeMatrixClient() val roomMember = aRoomMember() val presenter = createRoomMemberDetailsPresenter( + room = aMatrixRoom( + getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) }, + userDisplayNameResult = { Result.success("Alice") }, + userAvatarUrlResult = { Result.success("anAvatarUrl") }, + ), client = client, roomMemberId = roomMember.userId ) @@ -199,13 +219,21 @@ class RoomMemberDetailsPresenterTest { fun `present - BlockUser with error`() = runTest { val matrixClient = FakeMatrixClient() matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE)) - val presenter = createRoomMemberDetailsPresenter(client = matrixClient) + val presenter = createRoomMemberDetailsPresenter( + client = matrixClient, + room = aMatrixRoom( + getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) }, + userDisplayNameResult = { Result.success("Alice") }, + userAvatarUrlResult = { Result.success("anAvatarUrl") }, + ), + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitFirstItem() initialState.eventSink(UserProfileEvents.BlockUser(needsConfirmation = false)) assertThat(awaitItem().isBlocked.isLoading()).isTrue() + skipItems(2) val errorState = awaitItem() assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE) // Clear error @@ -218,13 +246,21 @@ class RoomMemberDetailsPresenterTest { fun `present - UnblockUser with error`() = runTest { val matrixClient = FakeMatrixClient() matrixClient.givenUnignoreUserResult(Result.failure(A_THROWABLE)) - val presenter = createRoomMemberDetailsPresenter(client = matrixClient) + val presenter = createRoomMemberDetailsPresenter( + room = aMatrixRoom( + getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) }, + userDisplayNameResult = { Result.success("Alice") }, + userAvatarUrlResult = { Result.success("anAvatarUrl") }, + ), + client = matrixClient, + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitFirstItem() initialState.eventSink(UserProfileEvents.UnblockUser(needsConfirmation = false)) assertThat(awaitItem().isBlocked.isLoading()).isTrue() + skipItems(2) val errorState = awaitItem() assertThat(errorState.isBlocked.errorOrNull()).isEqualTo(A_THROWABLE) // Clear error @@ -235,7 +271,13 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest { - val presenter = createRoomMemberDetailsPresenter() + val presenter = createRoomMemberDetailsPresenter( + room = aMatrixRoom( + getUpdatedMemberResult = { Result.failure(AN_EXCEPTION) }, + userDisplayNameResult = { Result.success("Alice") }, + userAvatarUrlResult = { Result.success("anAvatarUrl") }, + ), + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -255,7 +297,14 @@ class RoomMemberDetailsPresenterTest { @Test fun `present - start DM action complete scenario`() = runTest { val startDMAction = FakeStartDMAction() - val presenter = createRoomMemberDetailsPresenter(startDMAction = startDMAction) + val presenter = createRoomMemberDetailsPresenter( + room = aMatrixRoom( + getUpdatedMemberResult = { Result.success(aRoomMember(displayName = "Alice")) }, + userDisplayNameResult = { Result.success("Alice") }, + userAvatarUrlResult = { Result.success("anAvatarUrl") }, + ), + startDMAction = startDMAction, + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -268,6 +317,7 @@ class RoomMemberDetailsPresenterTest { startDMAction.givenExecuteResult(startDMFailureResult) initialState.eventSink(UserProfileEvents.StartDM) assertThat(awaitItem().startDmActionState).isInstanceOf(AsyncAction.Loading::class.java) + skipItems(2) awaitItem().also { state -> assertThat(state.startDmActionState).isEqualTo(startDMFailureResult) state.eventSink(UserProfileEvents.ClearStartDMState) @@ -292,8 +342,8 @@ class RoomMemberDetailsPresenterTest { } private fun createRoomMemberDetailsPresenter( + room: MatrixRoom, client: MatrixClient = FakeMatrixClient(), - room: MatrixRoom = aMatrixRoom(), roomMemberId: UserId = UserId("@alice:server.org"), startDMAction: StartDMAction = FakeStartDMAction() ): RoomMemberDetailsPresenter { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt index ae789f8958..5f704a23d4 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/moderation/DefaultRoomMembersModerationPresenterTest.kt @@ -54,29 +54,34 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `canDisplayModerationActions - when user can kick other users, FF is enabled and room is not a DM returns true`() = runTest { - val room = FakeMatrixRoom(isDirect = false, activeMemberCount = 10).apply { - givenCanKickResult(Result.success(true)) - } + val room = FakeMatrixRoom( + isDirect = false, + activeMemberCount = 10, + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + ) val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) assertThat(presenter.canDisplayModerationActions()).isTrue() } @Test fun `canDisplayModerationActions - when user can ban other users, FF is enabled and room is not a DM returns true`() = runTest { - val room = FakeMatrixRoom(isDirect = false, activeMemberCount = 10).apply { - givenCanBanResult(Result.success(true)) - } + val room = FakeMatrixRoom( + isDirect = false, + activeMemberCount = 10, + canBanResult = { Result.success(true) }, + ) val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) assertThat(presenter.canDisplayModerationActions()).isTrue() } @Test fun `present - SelectRoomMember when the current user has permissions displays member actions`() = runTest { - val room = FakeMatrixRoom().apply { - givenCanKickResult(Result.success(true)) - givenCanBanResult(Result.success(true)) - givenUserRoleResult(Result.success(RoomMember.Role.ADMIN)) - } + val room = FakeMatrixRoom( + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, + ) val selectedMember = aVictor() val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { @@ -98,11 +103,12 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `present - SelectRoomMember displays only view profile if selected member has same power level as the current user`() = runTest { - val room = FakeMatrixRoom(sessionId = A_USER_ID).apply { - givenCanKickResult(Result.success(true)) - givenCanBanResult(Result.success(true)) - givenUserRoleResult(Result.success(RoomMember.Role.ADMIN)) - } + val room = FakeMatrixRoom( + sessionId = A_USER_ID, + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, + ) val selectedMember = aRoomMember(A_USER_ID_2, powerLevel = 100L) val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { @@ -123,11 +129,11 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `present - SelectRoomMember displays an unban confirmation dialog when the member is banned`() = runTest { val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN) - val room = FakeMatrixRoom().apply { - givenCanKickResult(Result.success(true)) - givenCanBanResult(Result.success(true)) - givenUserRoleResult(Result.success(RoomMember.Role.ADMIN)) - } + val room = FakeMatrixRoom( + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, + ) val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -144,11 +150,12 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `present - Kick removes the user`() = runTest { val analyticsService = FakeAnalyticsService() - val room = FakeMatrixRoom().apply { - givenCanKickResult(Result.success(true)) - givenCanBanResult(Result.success(true)) - givenUserRoleResult(Result.success(RoomMember.Role.ADMIN)) - } + val room = FakeMatrixRoom( + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, + kickUserResult = { _, _ -> Result.success(Unit) }, + ) val selectedMember = aVictor() val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService) moleculeFlow(RecompositionMode.Immediate) { @@ -171,11 +178,12 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `present - BanUser requires confirmation and then bans the user`() = runTest { val analyticsService = FakeAnalyticsService() - val room = FakeMatrixRoom().apply { - givenCanKickResult(Result.success(true)) - givenCanBanResult(Result.success(true)) - givenUserRoleResult(Result.success(RoomMember.Role.ADMIN)) - } + val room = FakeMatrixRoom( + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, + banUserResult = { _, _ -> Result.success(Unit) }, + ) val selectedMember = aVictor() val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService) moleculeFlow(RecompositionMode.Immediate) { @@ -204,11 +212,13 @@ class DefaultRoomMembersModerationPresenterTest { fun `present - UnbanUser requires confirmation and then unbans the user`() = runTest { val analyticsService = FakeAnalyticsService() val selectedMember = aRoomMember(A_USER_ID_2, membership = RoomMembershipState.BAN) - val room = FakeMatrixRoom().apply { - givenCanKickResult(Result.success(true)) - givenCanBanResult(Result.success(true)) + val room = FakeMatrixRoom( + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + userRoleResult = { Result.success(RoomMember.Role.ADMIN) }, + unBanUserResult = { _, _ -> Result.success(Unit) }, + ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(selectedMember))) - givenUserRoleResult(Result.success(RoomMember.Role.ADMIN)) } val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService) moleculeFlow(RecompositionMode.Immediate) { @@ -231,10 +241,11 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `present - Reset removes the selected user and actions`() = runTest { - val room = FakeMatrixRoom().apply { - givenCanKickResult(Result.success(true)) - givenCanBanResult(Result.success(true)) - } + val room = FakeMatrixRoom( + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + userRoleResult = { Result.success(RoomMember.Role.USER) }, + ) val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -251,13 +262,14 @@ class DefaultRoomMembersModerationPresenterTest { @Test fun `present - Reset resets any async actions`() = runTest { - val room = FakeMatrixRoom().apply { - givenCanKickResult(Result.success(true)) - givenCanBanResult(Result.success(true)) - givenKickUserResult(Result.failure(Throwable("Eek"))) - givenBanUserResult(Result.failure(Throwable("Eek"))) - givenUnbanUserResult(Result.failure(Throwable("Eek"))) - } + val room = FakeMatrixRoom( + canKickResult = { Result.success(true) }, + canBanResult = { Result.success(true) }, + kickUserResult = { _, _ -> Result.failure(Throwable("Eek")) }, + banUserResult = { _, _ -> Result.failure(Throwable("Eek")) }, + unBanUserResult = { _, _ -> Result.failure(Throwable("Eek")) }, + userRoleResult = { Result.success(RoomMember.Role.USER) }, + ) val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt index 35353f0d1a..c2eb07002c 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/RolesAndPermissionPresenterTest.kt @@ -27,6 +27,7 @@ import io.element.android.libraries.architecture.AsyncAction import io.element.android.libraries.core.coroutine.CoroutineDispatchers import io.element.android.libraries.matrix.api.room.RoomMember import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.defaultRoomPowerLevels import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.testCoroutineDispatchers import kotlinx.coroutines.ExperimentalCoroutinesApi @@ -67,7 +68,12 @@ class RolesAndPermissionPresenterTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `present - DemoteSelfTo changes own role to the specified one`() = runTest(StandardTestDispatcher()) { - val presenter = createRolesAndPermissionsPresenter(dispatchers = testCoroutineDispatchers()) + val presenter = createRolesAndPermissionsPresenter( + dispatchers = testCoroutineDispatchers(), + room = FakeMatrixRoom( + updateUserRoleResult = { Result.success(Unit) } + ), + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -85,9 +91,9 @@ class RolesAndPermissionPresenterTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `present - DemoteSelfTo can handle failures and clean them`() = runTest(StandardTestDispatcher()) { - val room = FakeMatrixRoom().apply { - givenUpdateUserRoleResult(Result.failure(Exception("Failed to update role"))) - } + val room = FakeMatrixRoom( + updateUserRoleResult = { Result.failure(Exception("Failed to update role")) } + ) val presenter = createRolesAndPermissionsPresenter(room = room, dispatchers = testCoroutineDispatchers()) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -123,7 +129,12 @@ class RolesAndPermissionPresenterTest { @Test fun `present - ResetPermissions needs confirmation, then resets permissions`() = runTest { val analyticsService = FakeAnalyticsService() - val presenter = createRolesAndPermissionsPresenter(analyticsService = analyticsService) + val presenter = createRolesAndPermissionsPresenter( + analyticsService = analyticsService, + room = FakeMatrixRoom( + resetPowerLevelsResult = { Result.success(defaultRoomPowerLevels()) } + ) + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt index d4155d9924..71223c8ff3 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/changeroles/ChangeRolesPresenterTest.kt @@ -275,7 +275,10 @@ class ChangeRolesPresenterTest { @Test fun `present - Save will display a confirmation when adding admins`() = runTest { - val room = FakeMatrixRoom().apply { + val room = FakeMatrixRoom( + updateUserRoleResult = { Result.success(Unit) }, + updateMembersResult = { Result.success(Unit) }, + ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList())) givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 100))) } @@ -325,7 +328,10 @@ class ChangeRolesPresenterTest { @Test fun `present - Save will just save the data for moderators`() = runTest { val analyticsService = FakeAnalyticsService() - val room = FakeMatrixRoom().apply { + val room = FakeMatrixRoom( + updateUserRoleResult = { Result.success(Unit) }, + updateMembersResult = { Result.success(Unit) }, + ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList())) givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 50))) } @@ -351,10 +357,11 @@ class ChangeRolesPresenterTest { @Test fun `present - Save can handle failures and ClearError clears them`() = runTest { - val room = FakeMatrixRoom().apply { + val room = FakeMatrixRoom( + updateUserRoleResult = { Result.failure(IllegalStateException("Failed")) } + ).apply { givenRoomMembersState(MatrixRoomMembersState.Ready(aRoomMemberList())) givenRoomInfo(aRoomInfo(userPowerLevels = persistentMapOf(A_USER_ID to 50))) - givenUpdateUserRoleResult(Result.failure(IllegalStateException("Failed"))) } val presenter = createChangeRolesPresenter(role = RoomMember.Role.MODERATOR, room = room) moleculeFlow(RecompositionMode.Immediate) { diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt index c37d458d67..5d2ae18783 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/rolesandpermissions/permissions/ChangeRoomPermissionsPresenterTest.kt @@ -164,7 +164,13 @@ class ChangeRoomPermissionsPresenterTest { @Test fun `present - Save updates the current permissions and resets hasChanges`() = runTest { val analyticsService = FakeAnalyticsService() - val presenter = createChangeRoomPermissionsPresenter(analyticsService = analyticsService) + val presenter = createChangeRoomPermissionsPresenter( + analyticsService = analyticsService, + room = FakeMatrixRoom( + updatePowerLevelsResult = { Result.success(Unit) }, + powerLevelsResult = { Result.success(defaultPermissions()) } + ), + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -208,9 +214,9 @@ class ChangeRoomPermissionsPresenterTest { @Test fun `present - Save will fail if there are not current permissions`() = runTest { - val room = FakeMatrixRoom().apply { - givenPowerLevelsResult(Result.failure(IllegalStateException("Failed to load power levels"))) - } + val room = FakeMatrixRoom( + powerLevelsResult = { Result.failure(IllegalStateException("Failed to load power levels")) } + ) val presenter = createChangeRoomPermissionsPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -225,9 +231,10 @@ class ChangeRoomPermissionsPresenterTest { @Test fun `present - Save can handle failures and they can be cleared`() = runTest { - val room = FakeMatrixRoom().apply { - givenUpdatePowerLevelsResult(Result.failure(IllegalStateException("Failed to update power levels"))) - } + val room = FakeMatrixRoom( + powerLevelsResult = { Result.success(defaultPermissions()) }, + updatePowerLevelsResult = { Result.failure(IllegalStateException("Failed to update power levels")) }, + ) val presenter = createChangeRoomPermissionsPresenter(room = room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() @@ -292,7 +299,9 @@ class ChangeRoomPermissionsPresenterTest { private fun createChangeRoomPermissionsPresenter( section: ChangeRoomPermissionsSection = ChangeRoomPermissionsSection.RoomDetails, - room: FakeMatrixRoom = FakeMatrixRoom(), + room: FakeMatrixRoom = FakeMatrixRoom( + powerLevelsResult = { Result.success(defaultPermissions()) } + ), analyticsService: FakeAnalyticsService = FakeAnalyticsService(), ) = ChangeRoomPermissionsPresenter( section = section, diff --git a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt index b48a603a85..7a9a8b2744 100644 --- a/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt +++ b/features/roomlist/impl/src/test/kotlin/io/element/android/features/roomlist/impl/RoomListPresenterTest.kt @@ -441,7 +441,10 @@ class RoomListPresenterTest { @Test fun `present - when set is favorite event is emitted, then the action is called`() = runTest { val scope = CoroutineScope(coroutineContext + SupervisorJob()) - val room = FakeMatrixRoom() + val setIsFavoriteResult = lambdaRecorder { _: Boolean -> Result.success(Unit) } + val room = FakeMatrixRoom( + setIsFavoriteResult = setIsFavoriteResult + ) val analyticsService = FakeAnalyticsService() val client = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, room) @@ -452,9 +455,13 @@ class RoomListPresenterTest { }.test { val initialState = awaitItem() initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, true)) - assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true)) + setIsFavoriteResult.assertions().isCalledOnce().with(value(true)) initialState.eventSink(RoomListEvents.SetRoomIsFavorite(A_ROOM_ID, false)) - assertThat(room.setIsFavoriteCalls).isEqualTo(listOf(true, false)) + setIsFavoriteResult.assertions().isCalledExactly(2) + .withSequence( + listOf(value(true)), + listOf(value(false)), + ) assertThat(analyticsService.capturedEvents).containsExactly( Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle), Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle) diff --git a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt index 42c053bafb..8c91ea530c 100644 --- a/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt +++ b/features/share/impl/src/test/kotlin/io/element/android/features/share/impl/SharePresenterTest.kt @@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.test.A_MESSAGE import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaupload.api.MediaPreProcessor import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor @@ -92,7 +93,9 @@ class SharePresenterTest { @Test fun `present - send text ok`() = runTest { - val matrixRoom = FakeMatrixRoom() + val matrixRoom = FakeMatrixRoom( + sendMessageResult = { _, _, _ -> Result.success(Unit) }, + ) val matrixClient = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, matrixRoom) } @@ -117,7 +120,9 @@ class SharePresenterTest { @Test fun `present - send media ok`() = runTest { - val matrixRoom = FakeMatrixRoom() + val matrixRoom = FakeMatrixRoom( + sendMediaResult = { Result.success(FakeMediaUploadHandler()) }, + ) val matrixClient = FakeMatrixClient().apply { givenGetRoomResult(A_ROOM_ID, matrixRoom) } diff --git a/gradle.properties b/gradle.properties index 9808f9eda1..b1f6468108 100644 --- a/gradle.properties +++ b/gradle.properties @@ -49,7 +49,7 @@ signing.element.nightly.keyPassword=Secret # Customise the Lint version to use a more recent version than the one bundled with AGP # https://googlesamples.github.io/android-custom-lint-rules/usage/newer-lint.md.html -android.experimental.lint.version=8.5.0-alpha07 +android.experimental.lint.version=8.7.0-alpha01 # Enable test fixture for all modules by default android.experimental.enableTestFixtures=true diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt index d7a4b105b4..21c50e51a4 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/room/FakeMatrixRoom.kt @@ -56,7 +56,7 @@ import io.element.android.libraries.matrix.test.A_SESSION_ID import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.timeline.FakeTimeline -import io.element.android.libraries.matrix.test.widget.FakeMatrixWidgetDriver +import io.element.android.tests.testutils.lambda.lambdaError import io.element.android.tests.testutils.simulateLongTask import kotlinx.collections.immutable.ImmutableMap import kotlinx.collections.immutable.persistentMapOf @@ -84,126 +84,88 @@ class FakeMatrixRoom( override val activeMemberCount: Long = 234L, val notificationSettingsService: NotificationSettingsService = FakeNotificationSettingsService(), override val liveTimeline: Timeline = FakeTimeline(), - private var roomPermalinkResult: () -> Result = { Result.success("room link") }, - private var eventPermalinkResult: (EventId) -> Result = { Result.success("event link") }, - var sendCallNotificationIfNeededResult: () -> Result = { Result.success(Unit) }, - canRedactOwn: Boolean = false, - canRedactOther: Boolean = false, + private var roomPermalinkResult: () -> Result = { lambdaError() }, + private var eventPermalinkResult: (EventId) -> Result = { lambdaError() }, + private val sendCallNotificationIfNeededResult: () -> Result = { lambdaError() }, + private val userDisplayNameResult: () -> Result = { lambdaError() }, + private val userAvatarUrlResult: () -> Result = { lambdaError() }, + private val userRoleResult: () -> Result = { lambdaError() }, + private val getUpdatedMemberResult: (UserId) -> Result = { lambdaError() }, + private val joinRoomResult: () -> Result = { lambdaError() }, + private val inviteUserResult: (UserId) -> Result = { lambdaError() }, + private val canInviteResult: (UserId) -> Result = { lambdaError() }, + private val canKickResult: (UserId) -> Result = { lambdaError() }, + private val canBanResult: (UserId) -> Result = { lambdaError() }, + private val canRedactOwnResult: (UserId) -> Result = { lambdaError() }, + private val canRedactOtherResult: (UserId) -> Result = { lambdaError() }, + private val canSendStateResult: (UserId, StateEventType) -> Result = { _, _ -> lambdaError() }, + private val canUserSendMessageResult: (UserId, MessageEventType) -> Result = { _, _ -> lambdaError() }, + private val sendMediaResult: (ProgressCallback?) -> Result = { lambdaError() }, + private val setNameResult: (String) -> Result = { lambdaError() }, + private val setTopicResult: (String) -> Result = { lambdaError() }, + private val updateAvatarResult: (String, ByteArray) -> Result = { _, _ -> lambdaError() }, + private val removeAvatarResult: () -> Result = { lambdaError() }, + private val sendMessageResult: (String, String?, List) -> Result = { _, _, _ -> lambdaError() }, + private val updateUserRoleResult: () -> Result = { lambdaError() }, + private val toggleReactionResult: (String, EventId) -> Result = { _, _ -> lambdaError() }, + private val retrySendMessageResult: (TransactionId) -> Result = { lambdaError() }, + private val cancelSendResult: (TransactionId) -> Result = { lambdaError() }, + private val forwardEventResult: (EventId, List) -> Result = { _, _ -> lambdaError() }, + private val reportContentResult: (EventId, String, UserId?) -> Result = { _, _, _ -> lambdaError() }, + private val kickUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() }, + private val banUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() }, + private val unBanUserResult: (UserId, String?) -> Result = { _, _ -> lambdaError() }, + private val sendLocationResult: (String, String, String?, Int?, AssetType?) -> Result = { _, _, _, _, _ -> lambdaError() }, + private val createPollResult: (String, List, Int, PollKind) -> Result = { _, _, _, _ -> lambdaError() }, + private val editPollResult: (EventId, String, List, Int, PollKind) -> Result = { _, _, _, _, _ -> lambdaError() }, + private val sendPollResponseResult: (EventId, List) -> Result = { _, _ -> lambdaError() }, + private val endPollResult: (EventId, String) -> Result = { _, _ -> lambdaError() }, + private val progressCallbackValues: List> = emptyList(), + private val generateWidgetWebViewUrlResult: (MatrixWidgetSettings, String, String?, String?) -> Result = { _, _, _, _ -> lambdaError() }, + private val getWidgetDriverResult: (MatrixWidgetSettings) -> Result = { lambdaError() }, + private val canUserTriggerRoomNotificationResult: (UserId) -> Result = { lambdaError() }, + private val canUserJoinCallResult: (UserId) -> Result = { lambdaError() }, + private val setIsFavoriteResult: (Boolean) -> Result = { lambdaError() }, + private val powerLevelsResult: () -> Result = { lambdaError() }, + private val updatePowerLevelsResult: () -> Result = { lambdaError() }, + private val resetPowerLevelsResult: () -> Result = { lambdaError() }, + private val typingNoticeResult: (Boolean) -> Result = { lambdaError() }, + private val leaveRoomLambda: () -> Result = { lambdaError() }, + private val updateMembersResult: () -> Unit = { lambdaError() }, + private val getMembersResult: (Int) -> Result> = { lambdaError() }, + private val timelineFocusedOnEventResult: (EventId) -> Result = { lambdaError() }, + private val setSendQueueEnabledLambda: (Boolean) -> Unit = { _: Boolean -> }, + private val saveComposerDraftLambda: (ComposerDraft) -> Result = { _: ComposerDraft -> Result.success(Unit) }, + private val loadComposerDraftLambda: () -> Result = { Result.success(null) }, + private val clearComposerDraftLambda: () -> Result = { Result.success(Unit) }, ) : MatrixRoom { - private var ignoreResult: Result = Result.success(Unit) - private var unignoreResult: Result = Result.success(Unit) - private var userDisplayNameResult = Result.success(null) - private var userAvatarUrlResult = Result.success(null) - private var userRoleResult = Result.success(RoomMember.Role.USER) - private var getRoomMemberResult = Result.failure(IllegalStateException("Member not found")) - private var joinRoomResult = Result.success(Unit) - private var inviteUserResult = Result.success(Unit) - private var canInviteResult = Result.success(true) - private var canKickResult = Result.success(false) - private var canBanResult = Result.success(false) - private var canRedactOwnResult = Result.success(canRedactOwn) - private var canRedactOtherResult = Result.success(canRedactOther) - private val canSendStateResults = mutableMapOf>() - private val canSendEventResults = mutableMapOf>() - private var sendMediaResult = Result.success(FakeMediaUploadHandler()) - private var setNameResult = Result.success(Unit) - private var setTopicResult = Result.success(Unit) - private var updateAvatarResult = Result.success(Unit) - private var removeAvatarResult = Result.success(Unit) - private var updateUserRoleResult = Result.success(Unit) - private var toggleReactionResult = Result.success(Unit) - private var retrySendMessageResult = Result.success(Unit) - private var cancelSendResult = Result.success(true) - private var forwardEventResult = Result.success(Unit) - private var reportContentResult = Result.success(Unit) - private var kickUserResult = Result.success(Unit) - private var banUserResult = Result.success(Unit) - private var unBanUserResult = Result.success(Unit) - private var sendLocationResult = Result.success(Unit) - private var createPollResult = Result.success(Unit) - private var editPollResult = Result.success(Unit) - private var sendPollResponseResult = Result.success(Unit) - private var endPollResult = Result.success(Unit) - private var progressCallbackValues = emptyList>() - private var generateWidgetWebViewUrlResult = Result.success("https://call.element.io") - private var getWidgetDriverResult: Result = Result.success(FakeMatrixWidgetDriver()) - private var canUserTriggerRoomNotificationResult: Result = Result.success(true) - private var canUserJoinCallResult: Result = Result.success(true) - private var setIsFavoriteResult = Result.success(Unit) - private var powerLevelsResult = Result.success(defaultRoomPowerLevels()) - private var updatePowerLevelsResult = Result.success(Unit) - private var resetPowerLevelsResult = Result.success(defaultRoomPowerLevels()) - var sendMessageMentions = emptyList() - private val _typingRecord = mutableListOf() - val typingRecord: List - get() = _typingRecord - - var sendMediaCount = 0 - private set - - private val _myReactions = mutableSetOf() - val myReactions: Set = _myReactions - - var retrySendMessageCount: Int = 0 - private set - - var cancelSendCount: Int = 0 - private set - - var reportedContentCount: Int = 0 - private set - - private val _sentLocations = mutableListOf() - val sentLocations: List = _sentLocations - - private val _createPollInvocations = mutableListOf() - val createPollInvocations: List = _createPollInvocations - - private val _editPollInvocations = mutableListOf() - val editPollInvocations: List = _editPollInvocations - - private val _sendPollResponseInvocations = mutableListOf() - val sendPollResponseInvocations: List = _sendPollResponseInvocations - - private val _endPollInvocations = mutableListOf() - val endPollInvocations: List = _endPollInvocations - - var invitedUserId: UserId? = null - private set - - var newTopic: String? = null - private set - - var newName: String? = null - private set - - var newAvatarData: ByteArray? = null - private set - - var removedAvatar: Boolean = false - private set - - var leaveRoomLambda: (() -> Result) = { Result.success(Unit) } - private val _roomInfoFlow: MutableSharedFlow = MutableSharedFlow(replay = 1) override val roomInfoFlow: Flow = _roomInfoFlow + fun givenRoomInfo(roomInfo: MatrixRoomInfo) { + _roomInfoFlow.tryEmit(roomInfo) + } + private val _roomTypingMembersFlow: MutableSharedFlow> = MutableSharedFlow(replay = 1) override val roomTypingMembersFlow: Flow> = _roomTypingMembersFlow + fun givenRoomTypingMembers(typingMembers: List) { + _roomTypingMembersFlow.tryEmit(typingMembers) + } + override val membersStateFlow: MutableStateFlow = MutableStateFlow(MatrixRoomMembersState.Unknown) override val roomNotificationSettingsStateFlow: MutableStateFlow = MutableStateFlow(MatrixRoomNotificationSettingsState.Unknown) - override suspend fun updateMembers() = Unit + override suspend fun updateMembers() = updateMembersResult() override suspend fun getUpdatedMember(userId: UserId): Result { - return getRoomMemberResult + return getUpdatedMemberResult(userId) } override suspend fun getMembers(limit: Int): Result> { - return Result.success(emptyList()) + return getMembersResult(limit) } override suspend fun updateRoomNotificationSettings(): Result = simulateLongTask { @@ -214,76 +176,56 @@ class FakeMatrixRoom( override val syncUpdateFlow: StateFlow = MutableStateFlow(0L) - private var timelineFocusedOnEventResult: Result = Result.success(FakeTimeline()) - - fun givenTimelineFocusedOnEventResult(result: Result) { - timelineFocusedOnEventResult = result - } - override suspend fun timelineFocusedOnEvent(eventId: EventId): Result = simulateLongTask { - timelineFocusedOnEventResult + timelineFocusedOnEventResult(eventId) } override suspend fun subscribeToSync() = Unit override suspend fun powerLevels(): Result { - return powerLevelsResult + return powerLevelsResult() } override suspend fun updatePowerLevels(matrixRoomPowerLevels: MatrixRoomPowerLevels): Result = simulateLongTask { - updatePowerLevelsResult + updatePowerLevelsResult() } override suspend fun resetPowerLevels(): Result = simulateLongTask { - resetPowerLevelsResult + resetPowerLevelsResult() } override fun destroy() = Unit override suspend fun userDisplayName(userId: UserId): Result = simulateLongTask { - userDisplayNameResult + userDisplayNameResult() } override suspend fun userAvatarUrl(userId: UserId): Result = simulateLongTask { - userAvatarUrlResult + userAvatarUrlResult() } override suspend fun userRole(userId: UserId): Result { - return userRoleResult + return userRoleResult() } override suspend fun updateUsersRoles(changes: List): Result { - return updateUserRoleResult + return updateUserRoleResult() } override suspend fun sendMessage(body: String, htmlBody: String?, mentions: List) = simulateLongTask { - sendMessageMentions = mentions - Result.success(Unit) + sendMessageResult(body, htmlBody, mentions) } override suspend fun toggleReaction(emoji: String, eventId: EventId): Result { - if (toggleReactionResult.isFailure) { - // Don't do the toggle if we failed - return toggleReactionResult - } - - if (_myReactions.contains(emoji)) { - _myReactions.remove(emoji) - } else { - _myReactions.add(emoji) - } - - return toggleReactionResult + return toggleReactionResult(emoji, eventId) } override suspend fun retrySendMessage(transactionId: TransactionId): Result { - retrySendMessageCount++ - return retrySendMessageResult + return retrySendMessageResult(transactionId) } override suspend fun cancelSend(transactionId: TransactionId): Result { - cancelSendCount++ - return cancelSendResult + return cancelSendResult(transactionId) } override suspend fun getPermalink(): Result { @@ -299,48 +241,47 @@ class FakeMatrixRoom( } override suspend fun join(): Result { - return joinRoomResult + return joinRoomResult() } override suspend fun inviteUserById(id: UserId): Result = simulateLongTask { - invitedUserId = id - inviteUserResult + inviteUserResult(id) } override suspend fun canUserBan(userId: UserId): Result { - return canBanResult + return canBanResult(userId) } override suspend fun canUserKick(userId: UserId): Result { - return canKickResult + return canKickResult(userId) } override suspend fun canUserInvite(userId: UserId): Result { - return canInviteResult + return canInviteResult(userId) } override suspend fun canUserRedactOwn(userId: UserId): Result { - return canRedactOwnResult + return canRedactOwnResult(userId) } override suspend fun canUserRedactOther(userId: UserId): Result { - return canRedactOtherResult + return canRedactOtherResult(userId) } override suspend fun canUserSendState(userId: UserId, type: StateEventType): Result { - return canSendStateResults[type] ?: Result.failure(IllegalStateException("No fake answer")) + return canSendStateResult(userId, type) } override suspend fun canUserSendMessage(userId: UserId, type: MessageEventType): Result { - return canSendEventResults[type] ?: Result.failure(IllegalStateException("No fake answer")) + return canUserSendMessageResult(userId, type) } override suspend fun canUserTriggerRoomNotification(userId: UserId): Result { - return canUserTriggerRoomNotificationResult + return canUserTriggerRoomNotificationResult(userId) } override suspend fun canUserJoinCall(userId: UserId): Result { - return canUserJoinCallResult + return canUserJoinCallResult(userId) } override suspend fun sendImage( @@ -376,37 +317,31 @@ class FakeMatrixRoom( ): Result = fakeSendMedia(progressCallback) override suspend fun forwardEvent(eventId: EventId, roomIds: List): Result = simulateLongTask { - forwardEventResult + forwardEventResult(eventId, roomIds) } private suspend fun fakeSendMedia(progressCallback: ProgressCallback?): Result = simulateLongTask { - sendMediaResult.onSuccess { - progressCallbackValues.forEach { (current, total) -> - progressCallback?.onProgress(current, total) - delay(1) - } - sendMediaCount++ + progressCallbackValues.forEach { (current, total) -> + progressCallback?.onProgress(current, total) + delay(1) } + sendMediaResult(progressCallback) } override suspend fun updateAvatar(mimeType: String, data: ByteArray): Result = simulateLongTask { - newAvatarData = data - updateAvatarResult + updateAvatarResult(mimeType, data) } override suspend fun removeAvatar(): Result = simulateLongTask { - removedAvatar = true - removeAvatarResult + removeAvatarResult() } override suspend fun setName(name: String): Result = simulateLongTask { - newName = name - setNameResult + setNameResult(name) } override suspend fun setTopic(topic: String): Result = simulateLongTask { - newTopic = topic - setTopicResult + setTopicResult(topic) } override suspend fun reportContent( @@ -414,28 +349,23 @@ class FakeMatrixRoom( reason: String, blockUserId: UserId? ): Result = simulateLongTask { - reportedContentCount++ - return reportContentResult + return reportContentResult(eventId, reason, blockUserId) } override suspend fun kickUser(userId: UserId, reason: String?): Result { - return kickUserResult + return kickUserResult(userId, reason) } override suspend fun banUser(userId: UserId, reason: String?): Result { - return banUserResult + return banUserResult(userId, reason) } override suspend fun unbanUser(userId: UserId, reason: String?): Result { - return unBanUserResult + return unBanUserResult(userId, reason) } - val setIsFavoriteCalls = mutableListOf() - override suspend fun setIsFavorite(isFavorite: Boolean): Result { - return setIsFavoriteResult.also { - setIsFavoriteCalls.add(isFavorite) - } + return setIsFavoriteResult(isFavorite) } val markAsReadCalls = mutableListOf() @@ -460,8 +390,13 @@ class FakeMatrixRoom( zoomLevel: Int?, assetType: AssetType?, ): Result = simulateLongTask { - _sentLocations.add(SendLocationInvocation(body, geoUri, description, zoomLevel, assetType)) - return sendLocationResult + return sendLocationResult( + body, + geoUri, + description, + zoomLevel, + assetType, + ) } override suspend fun createPoll( @@ -470,8 +405,12 @@ class FakeMatrixRoom( maxSelections: Int, pollKind: PollKind ): Result = simulateLongTask { - _createPollInvocations.add(SavePollInvocation(question, answers, maxSelections, pollKind)) - return createPollResult + return createPollResult( + question, + answers, + maxSelections, + pollKind, + ) } override suspend fun editPoll( @@ -481,24 +420,27 @@ class FakeMatrixRoom( maxSelections: Int, pollKind: PollKind ): Result = simulateLongTask { - _editPollInvocations.add(SavePollInvocation(question, answers, maxSelections, pollKind)) - return editPollResult + return editPollResult( + pollStartId, + question, + answers, + maxSelections, + pollKind, + ) } override suspend fun sendPollResponse( pollStartId: EventId, answers: List ): Result = simulateLongTask { - _sendPollResponseInvocations.add(SendPollResponseInvocation(pollStartId, answers)) - return sendPollResponseResult + return sendPollResponseResult(pollStartId, answers) } override suspend fun endPoll( pollStartId: EventId, text: String ): Result = simulateLongTask { - _endPollInvocations.add(EndPollInvocation(pollStartId, text)) - return endPollResult + return endPollResult(pollStartId, text) } override suspend fun sendVoiceMessage( @@ -509,8 +451,7 @@ class FakeMatrixRoom( ): Result = fakeSendMedia(progressCallback) override suspend fun typingNotice(isTyping: Boolean): Result { - _typingRecord += isTyping - return Result.success(Unit) + return typingNoticeResult(isTyping) } override suspend fun generateWidgetWebViewUrl( @@ -518,228 +459,34 @@ class FakeMatrixRoom( clientId: String, languageTag: String?, theme: String?, - ): Result = generateWidgetWebViewUrlResult + ): Result = generateWidgetWebViewUrlResult( + widgetSettings, + clientId, + languageTag, + theme, + ) override suspend fun sendCallNotificationIfNeeded(): Result { return sendCallNotificationIfNeededResult() } - var setSendQueueEnabledLambda = { _: Boolean -> } override suspend fun setSendQueueEnabled(enabled: Boolean) = setSendQueueEnabledLambda(enabled) - var saveComposerDraftLambda = { _: ComposerDraft -> Result.success(Unit) } override suspend fun saveComposerDraft(composerDraft: ComposerDraft) = saveComposerDraftLambda(composerDraft) - var loadComposerDraftLambda = { Result.success(null) } override suspend fun loadComposerDraft() = loadComposerDraftLambda() - var clearComposerDraftLambda = { Result.success(Unit) } override suspend fun clearComposerDraft() = clearComposerDraftLambda() - override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result = getWidgetDriverResult + override fun getWidgetDriver(widgetSettings: MatrixWidgetSettings): Result { + return getWidgetDriverResult(widgetSettings) + } fun givenRoomMembersState(state: MatrixRoomMembersState) { membersStateFlow.value = state } - - fun givenGetRoomMemberResult(result: Result) { - getRoomMemberResult = result - } - - fun givenUserDisplayNameResult(displayName: Result) { - userDisplayNameResult = displayName - } - - fun givenUserAvatarUrlResult(avatarUrl: Result) { - userAvatarUrlResult = avatarUrl - } - - fun givenUserRoleResult(role: Result) { - userRoleResult = role - } - - fun givenUpdateUserRoleResult(result: Result) { - updateUserRoleResult = result - } - - fun givenJoinRoomResult(result: Result) { - joinRoomResult = result - } - - fun givenCanKickResult(result: Result) { - canKickResult = result - } - - fun givenCanBanResult(result: Result) { - canBanResult = result - } - - fun givenInviteUserResult(result: Result) { - inviteUserResult = result - } - - fun givenCanInviteResult(result: Result) { - canInviteResult = result - } - - fun givenCanSendStateResult(type: StateEventType, result: Result) { - canSendStateResults[type] = result - } - - fun givenCanSendEventResult(type: MessageEventType, result: Result) { - canSendEventResults[type] = result - } - - fun givenCanTriggerRoomNotification(result: Result) { - canUserTriggerRoomNotificationResult = result - } - - fun givenCanUserJoinCall(result: Result) { - canUserJoinCallResult = result - } - - fun givenIgnoreResult(result: Result) { - ignoreResult = result - } - - fun givenUnIgnoreResult(result: Result) { - unignoreResult = result - } - - fun givenSendMediaResult(result: Result) { - sendMediaResult = result - } - - fun givenUpdateAvatarResult(result: Result) { - updateAvatarResult = result - } - - fun givenRemoveAvatarResult(result: Result) { - removeAvatarResult = result - } - - fun givenSetNameResult(result: Result) { - setNameResult = result - } - - fun givenSetTopicResult(result: Result) { - setTopicResult = result - } - - fun givenToggleReactionResult(result: Result) { - toggleReactionResult = result - } - - fun givenRetrySendMessageResult(result: Result) { - retrySendMessageResult = result - } - - fun givenCancelSendResult(result: Result) { - cancelSendResult = result - } - - fun givenForwardEventResult(result: Result) { - forwardEventResult = result - } - - fun givenReportContentResult(result: Result) { - reportContentResult = result - } - - fun givenKickUserResult(result: Result) { - kickUserResult = result - } - - fun givenBanUserResult(result: Result) { - banUserResult = result - } - - fun givenUnbanUserResult(result: Result) { - unBanUserResult = result - } - - fun givenSendLocationResult(result: Result) { - sendLocationResult = result - } - - fun givenCreatePollResult(result: Result) { - createPollResult = result - } - - fun givenEditPollResult(result: Result) { - editPollResult = result - } - - fun givenSendPollResponseResult(result: Result) { - sendPollResponseResult = result - } - - fun givenEndPollResult(result: Result) { - endPollResult = result - } - - fun givenProgressCallbackValues(values: List>) { - progressCallbackValues = values - } - - fun givenGenerateWidgetWebViewUrlResult(result: Result) { - generateWidgetWebViewUrlResult = result - } - - fun givenGetWidgetDriverResult(result: Result) { - getWidgetDriverResult = result - } - - fun givenSetIsFavoriteResult(result: Result) { - setIsFavoriteResult = result - } - - fun givenRoomInfo(roomInfo: MatrixRoomInfo) { - _roomInfoFlow.tryEmit(roomInfo) - } - - fun givenRoomTypingMembers(typingMembers: List) { - _roomTypingMembersFlow.tryEmit(typingMembers) - } - - fun givenPowerLevelsResult(result: Result) { - powerLevelsResult = result - } - - fun givenUpdatePowerLevelsResult(result: Result) { - updatePowerLevelsResult = result - } - - fun givenResetPowerLevelsResult(result: Result) { - resetPowerLevelsResult = result - } } -data class SendLocationInvocation( - val body: String, - val geoUri: String, - val description: String?, - val zoomLevel: Int?, - val assetType: AssetType?, -) - -data class SavePollInvocation( - val question: String, - val answers: List, - val maxSelections: Int, - val pollKind: PollKind, -) - -data class SendPollResponseInvocation( - val pollStartId: EventId, - val answers: List, -) - -data class EndPollInvocation( - val pollStartId: EventId, - val text: String, -) - fun aRoomInfo( id: RoomId = A_ROOM_ID, name: String? = A_ROOM_NAME, diff --git a/libraries/mediaupload/api/build.gradle.kts b/libraries/mediaupload/api/build.gradle.kts index eed11265c1..4887d01d72 100644 --- a/libraries/mediaupload/api/build.gradle.kts +++ b/libraries/mediaupload/api/build.gradle.kts @@ -40,6 +40,7 @@ android { testImplementation(projects.libraries.matrix.test) testImplementation(projects.libraries.mediaupload.test) + testImplementation(projects.tests.testutils) testImplementation(libs.test.junit) testImplementation(libs.test.truth) testImplementation(libs.coroutines.test) diff --git a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt index 20bdf034c1..21c5f0425a 100644 --- a/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt +++ b/libraries/mediaupload/api/src/test/kotlin/io/element/android/libraries/mediaupload/api/MediaSenderTest.kt @@ -19,9 +19,12 @@ package io.element.android.libraries.mediaupload.api import android.net.Uri import com.google.common.truth.Truth.assertThat import io.element.android.libraries.core.mimetype.MimeTypes +import io.element.android.libraries.matrix.api.core.ProgressCallback import io.element.android.libraries.matrix.api.room.MatrixRoom +import io.element.android.libraries.matrix.test.media.FakeMediaUploadHandler import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor +import io.element.android.tests.testutils.lambda.lambdaRecorder import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.launch import kotlinx.coroutines.test.StandardTestDispatcher @@ -46,13 +49,17 @@ class MediaSenderTest { @Test fun `given an attachment when sending it the MatrixRoom will call sendMedia`() = runTest { - val room = FakeMatrixRoom() + val sendMediaResult = lambdaRecorder> { + Result.success(FakeMediaUploadHandler()) + } + val room = FakeMatrixRoom( + sendMediaResult = sendMediaResult + ) val sender = aMediaSender(room = room) val uri = Uri.parse("content://image.jpg") sender.sendMedia(uri = uri, mimeType = MimeTypes.Jpeg, compressIfPossible = true) - - assertThat(room.sendMediaCount).isEqualTo(1) + sendMediaResult.assertions().isCalledOnce() } @Test @@ -70,9 +77,9 @@ class MediaSenderTest { @Test fun `given a failure in the media upload when sending the whole process fails`() = runTest { - val room = FakeMatrixRoom().apply { - givenSendMediaResult(Result.failure(Exception())) - } + val room = FakeMatrixRoom( + sendMediaResult = { Result.failure(Exception()) } + ) val sender = aMediaSender(room = room) val uri = Uri.parse("content://image.jpg") @@ -84,7 +91,9 @@ class MediaSenderTest { @OptIn(ExperimentalCoroutinesApi::class) @Test fun `given a cancellation in the media upload when sending the job is cancelled`() = runTest(StandardTestDispatcher()) { - val room = FakeMatrixRoom() + val room = FakeMatrixRoom( + sendMediaResult = { Result.success(FakeMediaUploadHandler()) } + ) val sender = aMediaSender(room = room) val sendJob = launch { val uri = Uri.parse("content://image.jpg") diff --git a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt index d4706461d8..f4dedfad97 100644 --- a/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt +++ b/libraries/push/impl/src/test/kotlin/io/element/android/libraries/push/impl/notifications/NotificationBroadcastReceiverHandlerTest.kt @@ -35,6 +35,7 @@ import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.FakeMatrixClientProvider import io.element.android.libraries.matrix.test.core.aBuildMeta import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.libraries.matrix.test.room.aRoomMember import io.element.android.libraries.matrix.test.timeline.FakeTimeline import io.element.android.libraries.preferences.api.store.SessionPreferencesStore import io.element.android.libraries.preferences.api.store.SessionPreferencesStoreFactory @@ -297,9 +298,9 @@ class NotificationBroadcastReceiverHandlerTest { @Test fun `Test reject room`() = runTest { val leaveRoom = lambdaRecorder> { Result.success(Unit) } - val matrixRoom = FakeMatrixRoom().apply { + val matrixRoom = FakeMatrixRoom( leaveRoomLambda = leaveRoom - } + ) val clearMembershipNotificationForRoomLambda = lambdaRecorder { _, _ -> } val fakeNotificationCleaner = FakeNotificationCleaner( clearMembershipNotificationForRoomLambda = clearMembershipNotificationForRoomLambda, @@ -342,7 +343,8 @@ class NotificationBroadcastReceiverHandlerTest { replyMessageLambda = replyMessage } val matrixRoom = FakeMatrixRoom( - liveTimeline = liveTimeline + liveTimeline = liveTimeline, + getUpdatedMemberResult = { Result.success(aRoomMember()) }, ) val onNotifiableEventReceivedResult = lambdaRecorder { _ -> } val onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceivedResult = onNotifiableEventReceivedResult) @@ -400,7 +402,8 @@ class NotificationBroadcastReceiverHandlerTest { replyMessageLambda = replyMessage } val matrixRoom = FakeMatrixRoom( - liveTimeline = liveTimeline + liveTimeline = liveTimeline, + getUpdatedMemberResult = { Result.success(aRoomMember()) }, ) val onNotifiableEventReceivedResult = lambdaRecorder { _ -> } val onNotifiableEventReceived = FakeOnNotifiableEventReceived(onNotifiableEventReceivedResult = onNotifiableEventReceivedResult)