From 3efbf4747dca556303e7ef7c79f65a36ceb759b9 Mon Sep 17 00:00:00 2001 From: ganfra Date: Thu, 30 Nov 2023 18:05:26 +0100 Subject: [PATCH] StartDM : add tests --- features/createroom/impl/build.gradle.kts | 1 + .../createroom/impl/DefaultStartDMAction.kt | 1 - .../impl/DefaultStartDMActionTests.kt | 84 +++++++++ .../impl/root/CreateRoomRootPresenterTests.kt | 164 ++++-------------- features/createroom/test/build.gradle.kts | 32 ++++ .../createroom/test/FakeStartDMAction.kt | 39 +++++ features/roomdetails/impl/build.gradle.kts | 1 + .../details/RoomMemberDetailsPresenter.kt | 3 - .../details/RoomMemberDetailsStateProvider.kt | 1 + .../members/details/RoomMemberDetailsView.kt | 3 +- .../impl/src/main/res/values/localazy.xml | 1 + .../roomdetails/RoomDetailsPresenterTests.kt | 59 ++++--- .../RoomMemberDetailsPresenterTests.kt | 88 ++++++++-- .../libraries/matrix/api/MatrixClient.kt | 2 +- .../libraries/matrix/api/room/StartDM.kt | 8 +- .../libraries/matrix/impl/RustMatrixClient.kt | 5 +- .../libraries/matrix/test/FakeMatrixClient.kt | 19 +- settings.gradle.kts | 4 +- tools/localazy/config.json | 3 +- 19 files changed, 321 insertions(+), 197 deletions(-) create mode 100644 features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt create mode 100644 features/createroom/test/build.gradle.kts create mode 100644 features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt diff --git a/features/createroom/impl/build.gradle.kts b/features/createroom/impl/build.gradle.kts index ae9818b727..6cd9b30741 100644 --- a/features/createroom/impl/build.gradle.kts +++ b/features/createroom/impl/build.gradle.kts @@ -67,6 +67,7 @@ dependencies { testImplementation(projects.libraries.mediaupload.test) testImplementation(projects.libraries.permissions.test) testImplementation(projects.libraries.usersearch.test) + testImplementation(projects.features.createroom.test) testImplementation(projects.tests.testutils) ksp(libs.showkase.processor) diff --git a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt index 0aa2e93dcb..b7f1dc6c9c 100644 --- a/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt +++ b/features/createroom/impl/src/main/kotlin/io/element/android/features/createroom/impl/DefaultStartDMAction.kt @@ -21,7 +21,6 @@ import com.squareup.anvil.annotations.ContributesBinding import im.vector.app.features.analytics.plan.CreatedRoom import io.element.android.features.createroom.api.StartDMAction import io.element.android.libraries.architecture.Async -import io.element.android.libraries.di.AppScope import io.element.android.libraries.di.SessionScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt new file mode 100644 index 0000000000..2bd7ebb687 --- /dev/null +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/DefaultStartDMActionTests.kt @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.createroom.impl + +import androidx.compose.runtime.mutableStateOf +import com.google.common.truth.Truth.assertThat +import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.StartDMResult +import io.element.android.libraries.matrix.test.A_FAILURE_REASON +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.A_USER_ID +import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.services.analytics.api.AnalyticsService +import io.element.android.services.analytics.test.FakeAnalyticsService +import kotlinx.coroutines.test.runTest +import org.junit.Test + +class DefaultStartDMActionTests { + + @Test + fun `when dm is found, assert state is updated with given room id`() = runTest { + val matrixClient = FakeMatrixClient().apply { + givenFindDmResult(A_ROOM_ID) + } + val action = createStartDMAction(matrixClient) + val state = mutableStateOf>(Async.Uninitialized) + action.execute(A_USER_ID, state) + assertThat(state.value).isEqualTo(Async.Success(A_ROOM_ID)) + } + + @Test + fun `when dm is not found, assert dm is created, state is updated with given room id and analytics get called`() = runTest { + val matrixClient = FakeMatrixClient().apply { + givenFindDmResult(null) + givenCreateDmResult(Result.success(A_ROOM_ID)) + } + val analyticsService = FakeAnalyticsService() + val action = createStartDMAction(matrixClient, analyticsService) + val state = mutableStateOf>(Async.Uninitialized) + action.execute(A_USER_ID, state) + assertThat(state.value).isEqualTo(Async.Success(A_ROOM_ID)) + assertThat(analyticsService.capturedEvents).containsExactly(CreatedRoom(isDM = true)) + } + + @Test + fun `when dm creation fails, assert state is updated with given error`() = runTest { + val matrixClient = FakeMatrixClient().apply { + givenFindDmResult(null) + givenCreateDmResult(Result.failure(A_THROWABLE)) + } + val action = createStartDMAction(matrixClient) + val state = mutableStateOf>(Async.Uninitialized) + action.execute(A_USER_ID, state) + assertThat(state.value).isEqualTo(Async.Failure(StartDMResult.Failure(A_FAILURE_REASON))) + } + + private fun createStartDMAction( + matrixClient: MatrixClient = FakeMatrixClient(), + analyticsService: AnalyticsService = FakeAnalyticsService(), + ): DefaultStartDMAction { + return DefaultStartDMAction( + matrixClient = matrixClient, + analyticsService = analyticsService, + ) + } +} diff --git a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt index bf4593bdbb..5322407c35 100644 --- a/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt +++ b/features/createroom/impl/src/test/kotlin/io/element/android/features/createroom/impl/root/CreateRoomRootPresenterTests.kt @@ -20,25 +20,22 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat -import im.vector.app.features.analytics.plan.CreatedRoom +import io.element.android.features.createroom.api.StartDMAction import io.element.android.features.createroom.impl.userlist.FakeUserListPresenter import io.element.android.features.createroom.impl.userlist.FakeUserListPresenterFactory import io.element.android.features.createroom.impl.userlist.UserListDataStore -import io.element.android.features.createroom.impl.userlist.aUserListState +import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.libraries.architecture.Async 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.StartDMResult import io.element.android.libraries.matrix.api.user.MatrixUser -import io.element.android.libraries.matrix.test.A_THROWABLE -import io.element.android.libraries.matrix.test.FakeMatrixClient +import io.element.android.libraries.matrix.test.A_FAILURE_REASON +import io.element.android.libraries.matrix.test.A_ROOM_ID import io.element.android.libraries.matrix.test.core.aBuildMeta -import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.usersearch.test.FakeUserRepository -import io.element.android.services.analytics.test.FakeAnalyticsService import io.element.android.tests.testutils.WarmUpRule -import kotlinx.collections.immutable.persistentListOf import kotlinx.coroutines.test.runTest -import org.junit.Before import org.junit.Rule import org.junit.Test @@ -47,142 +44,57 @@ class CreateRoomRootPresenterTests { @get:Rule val warmUpRule = WarmUpRule() - private lateinit var userRepository: FakeUserRepository - private lateinit var presenter: CreateRoomRootPresenter - private lateinit var fakeUserListPresenter: FakeUserListPresenter - private lateinit var fakeMatrixClient: FakeMatrixClient - private lateinit var fakeAnalyticsService: FakeAnalyticsService - - @Before - fun setup() { - fakeUserListPresenter = FakeUserListPresenter() - fakeMatrixClient = FakeMatrixClient() - fakeAnalyticsService = FakeAnalyticsService() - userRepository = FakeUserRepository() - presenter = CreateRoomRootPresenter( - presenterFactory = FakeUserListPresenterFactory(fakeUserListPresenter), - userRepository = userRepository, - userListDataStore = UserListDataStore(), - matrixClient = fakeMatrixClient, - analyticsService = fakeAnalyticsService, - buildMeta = aBuildMeta(), - ) - } - @Test - fun `present - initial state`() = runTest { + fun `present - start DM action complete scenario`() = runTest { + val startDMAction = FakeStartDMAction() + val presenter = createCreateRoomRootPresenter(startDMAction) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { val initialState = awaitItem() + assertThat(initialState.startDmAction).isInstanceOf(Async.Uninitialized::class.java) assertThat(initialState.applicationName).isEqualTo(aBuildMeta().applicationName) assertThat(initialState.userListState.selectedUsers).isEmpty() assertThat(initialState.userListState.isSearchActive).isFalse() assertThat(initialState.userListState.isMultiSelectionEnabled).isFalse() - } - } - - @Test - fun `present - trigger create DM action`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - val matrixUser = MatrixUser(UserId("@name:domain")) - val createDmResult = Result.success(RoomId("!createDmResult:domain")) - - fakeMatrixClient.givenFindDmResult(null) - fakeMatrixClient.givenCreateDmResult(createDmResult) - - initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) - val stateAfterStartDM = awaitItem() - assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java) - assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull()) - } - } - - @Test - fun `present - creating a DM records analytics event`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - val matrixUser = MatrixUser(UserId("@name:domain")) - val createDmResult = Result.success(RoomId("!createDmResult:domain")) - - fakeMatrixClient.givenFindDmResult(null) - fakeMatrixClient.givenCreateDmResult(createDmResult) - - initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - skipItems(2) - - val analyticsEvent = fakeAnalyticsService.capturedEvents.filterIsInstance().firstOrNull() - assertThat(analyticsEvent).isNotNull() - assertThat(analyticsEvent?.isDM).isTrue() - } - } - @Test - fun `present - trigger retrieve DM action`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() val matrixUser = MatrixUser(UserId("@name:domain")) - val fakeDmResult = FakeMatrixRoom(roomId = RoomId("!fakeDmResult:domain")) - - fakeMatrixClient.givenFindDmResult(fakeDmResult) - - initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - val stateAfterStartDM = awaitItem() - assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Success::class.java) - assertThat(stateAfterStartDM.startDmAction.dataOrNull()).isEqualTo(fakeDmResult.roomId) - assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance()).isEmpty() - } - } - - @Test - fun `present - trigger retry create DM action`() = runTest { - moleculeFlow(RecompositionMode.Immediate) { - presenter.present() - }.test { - val initialState = awaitItem() - val matrixUser = MatrixUser(UserId("@name:domain")) - val createDmResult = Result.success(RoomId("!createDmResult:domain")) - fakeUserListPresenter.givenState(aUserListState().copy(selectedUsers = persistentListOf(matrixUser))) - - fakeMatrixClient.givenFindDmResult(null) - fakeMatrixClient.givenCreateDmError(A_THROWABLE) - fakeMatrixClient.givenCreateDmResult(createDmResult) + val startDMSuccessResult = Async.Success(A_ROOM_ID) + val startDMFailureResult = Async.Failure(StartDMResult.Failure(A_FAILURE_REASON)) // Failure + startDMAction.givenExecuteResult(startDMFailureResult) initialState.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) - val stateAfterStartDM = awaitItem() - assertThat(stateAfterStartDM.startDmAction).isInstanceOf(Async.Failure::class.java) - assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance()).isEmpty() - - // Cancel - stateAfterStartDM.eventSink(CreateRoomRootEvents.CancelStartDM) - val stateAfterCancel = awaitItem() - assertThat(stateAfterCancel.startDmAction).isInstanceOf(Async.Uninitialized::class.java) - - // Failure - stateAfterCancel.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + awaitItem().also { state -> + assertThat(state.startDmAction).isEqualTo(startDMFailureResult) + state.eventSink(CreateRoomRootEvents.CancelStartDM) + } + + // Success + startDMAction.givenExecuteResult(startDMSuccessResult) + awaitItem().also { state -> + assertThat(state.startDmAction).isEqualTo(Async.Uninitialized) + state.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) + } assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) - val stateAfterSecondAttempt = awaitItem() - assertThat(stateAfterSecondAttempt.startDmAction).isInstanceOf(Async.Failure::class.java) - assertThat(fakeAnalyticsService.capturedEvents.filterIsInstance()).isEmpty() + awaitItem().also { state -> + assertThat(state.startDmAction).isEqualTo(startDMSuccessResult) + } - // Retry with success - fakeMatrixClient.givenCreateDmError(null) - stateAfterSecondAttempt.eventSink(CreateRoomRootEvents.StartDM(matrixUser)) - assertThat(awaitItem().startDmAction).isInstanceOf(Async.Loading::class.java) - val stateAfterRetryStartDM = awaitItem() - assertThat(stateAfterRetryStartDM.startDmAction).isInstanceOf(Async.Success::class.java) - assertThat(stateAfterRetryStartDM.startDmAction.dataOrNull()).isEqualTo(createDmResult.getOrNull()) } } + + private fun createCreateRoomRootPresenter( + startDMAction: StartDMAction = FakeStartDMAction(), + ): CreateRoomRootPresenter { + return CreateRoomRootPresenter( + presenterFactory = FakeUserListPresenterFactory(FakeUserListPresenter()), + userRepository = FakeUserRepository(), + userListDataStore = UserListDataStore(), + startDMAction = startDMAction, + buildMeta = aBuildMeta(), + ) + } } diff --git a/features/createroom/test/build.gradle.kts b/features/createroom/test/build.gradle.kts new file mode 100644 index 0000000000..7c83725971 --- /dev/null +++ b/features/createroom/test/build.gradle.kts @@ -0,0 +1,32 @@ +/* + * Copyright (c) 2022 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +plugins { + id("io.element.android-compose-library") +} + +android { + namespace = "io.element.android.features.createroom.test" +} + +dependencies { + implementation(libs.coroutines.core) + implementation(projects.libraries.matrix.api) + implementation(projects.libraries.matrix.test) + implementation(projects.tests.testutils) + implementation(projects.libraries.architecture) + api(projects.features.createroom.api) +} diff --git a/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt new file mode 100644 index 0000000000..09d59b7f6c --- /dev/null +++ b/features/createroom/test/src/main/kotlin/io/element/android/features/createroom/test/FakeStartDMAction.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.features.createroom.test + +import androidx.compose.runtime.MutableState +import io.element.android.features.createroom.api.StartDMAction +import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.core.UserId +import io.element.android.libraries.matrix.test.A_ROOM_ID +import io.element.android.tests.testutils.simulateLongTask + +class FakeStartDMAction : StartDMAction { + + private var executeResult: Async = Async.Success(A_ROOM_ID) + + fun givenExecuteResult(result: Async) { + executeResult = result + } + + override suspend fun execute(userId: UserId, actionState: MutableState>) = simulateLongTask { + actionState.value = Async.Loading() + actionState.value = executeResult + } +} diff --git a/features/roomdetails/impl/build.gradle.kts b/features/roomdetails/impl/build.gradle.kts index 44cbbb5cbb..826621b2b5 100644 --- a/features/roomdetails/impl/build.gradle.kts +++ b/features/roomdetails/impl/build.gradle.kts @@ -68,6 +68,7 @@ dependencies { testImplementation(projects.libraries.featureflag.test) testImplementation(projects.tests.testutils) testImplementation(projects.features.leaveroom.test) + testImplementation(projects.features.createroom.test) ksp(libs.showkase.processor) } diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt index 463fcf186a..e82b5f0cb5 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsPresenter.kt @@ -25,16 +25,13 @@ import androidx.compose.runtime.produceState import androidx.compose.runtime.remember import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.setValue -import com.squareup.anvil.annotations.ContributesBinding import dagger.assisted.Assisted -import dagger.assisted.AssistedFactory import dagger.assisted.AssistedInject import io.element.android.features.createroom.api.StartDMAction import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState.ConfirmationDialog import io.element.android.libraries.architecture.Async import io.element.android.libraries.architecture.Presenter import io.element.android.libraries.core.bool.orFalse -import io.element.android.libraries.di.RoomScope import io.element.android.libraries.matrix.api.MatrixClient import io.element.android.libraries.matrix.api.core.RoomId import io.element.android.libraries.matrix.api.core.UserId diff --git a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt index bf629e01d8..c5710986a6 100644 --- a/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt +++ b/features/roomdetails/impl/src/main/kotlin/io/element/android/features/roomdetails/impl/members/details/RoomMemberDetailsStateProvider.kt @@ -28,6 +28,7 @@ open class RoomMemberDetailsStateProvider : PreviewParameterProvider"Your homeserver does not support this option in encrypted rooms, you won\'t get notified in this room." "All messages" "In this room, notify me for" + "An error occurred when trying to start a chat" "Block" "Blocked users won\'t be able to send you messages and all their messages will be hidden. You can unblock them anytime." "Block user" diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt index 5ab79d2cee..577b01a974 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/RoomDetailsPresenterTests.kt @@ -20,6 +20,7 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth.assertThat +import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.features.leaveroom.api.LeaveRoomEvent import io.element.android.features.leaveroom.api.LeaveRoomPresenter import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter @@ -45,10 +46,11 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2 import io.element.android.libraries.matrix.test.FakeMatrixClient import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.room.FakeMatrixRoom +import io.element.android.tests.testutils.WarmUpRule import io.element.android.tests.testutils.consumeItemsUntilPredicate import io.element.android.tests.testutils.testCoroutineDispatchers -import io.element.android.tests.testutils.WarmUpRule import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.test.TestScope import kotlinx.coroutines.test.runTest import org.junit.Rule import org.junit.Test @@ -60,16 +62,16 @@ class RoomDetailsPresenterTests { @get:Rule val warmUpRule = WarmUpRule() - private fun createRoomDetailsPresenter( + private fun TestScope.createRoomDetailsPresenter( room: MatrixRoom, leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(), - dispatchers: CoroutineDispatchers, + dispatchers: CoroutineDispatchers = testCoroutineDispatchers(), notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService() ): RoomDetailsPresenter { val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService) val roomMemberDetailsPresenterFactory = object : RoomMemberDetailsPresenter.Factory { override fun create(roomMemberId: UserId): RoomMemberDetailsPresenter { - return RoomMemberDetailsPresenter(matrixClient, room, roomMemberId) + return RoomMemberDetailsPresenter(roomMemberId, matrixClient, room, FakeStartDMAction()) } } val featureFlagService = FakeFeatureFlagService( @@ -89,7 +91,7 @@ class RoomDetailsPresenterTests { @Test fun `present - initial state is created from room info`() = runTest { val room = aMatrixRoom() - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -108,7 +110,7 @@ class RoomDetailsPresenterTests { @Test fun `present - initial state with no room name`() = runTest { val room = aMatrixRoom(name = null) - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -130,7 +132,7 @@ class RoomDetailsPresenterTests { val roomMembers = listOf(myRoomMember, otherRoomMember) givenRoomMembersState(MatrixRoomMembersState.Ready(roomMembers)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -164,7 +166,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom().apply { givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -179,7 +181,7 @@ class RoomDetailsPresenterTests { val room = aMatrixRoom().apply { givenCanInviteResult(Result.failure(Throwable("Whoops"))) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -197,7 +199,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.failure(Throwable("Whelp"))) givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -226,7 +228,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true)) givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -241,6 +243,7 @@ class RoomDetailsPresenterTests { cancelAndIgnoreRemainingEvents() } } + @Test fun `present - initial state when in a DM with no topic`() = runTest { val myRoomMember = aRoomMember(A_SESSION_ID) @@ -255,7 +258,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_TOPIC, Result.success(true)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -276,7 +279,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(true)) givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -297,7 +300,7 @@ class RoomDetailsPresenterTests { givenCanSendStateResult(StateEventType.ROOM_AVATAR, Result.success(false)) givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -315,7 +318,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -333,7 +336,7 @@ class RoomDetailsPresenterTests { givenCanInviteResult(Result.success(false)) } - val presenter = createRoomDetailsPresenter(room, dispatchers = testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter(room) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -351,7 +354,11 @@ class RoomDetailsPresenterTests { fun `present - leave room event is passed on to leave room presenter`() = runTest { val leaveRoomPresenter = FakeLeaveRoomPresenter() val room = aMatrixRoom() - val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers()) + val presenter = createRoomDetailsPresenter( + room = room, + leaveRoomPresenter = leaveRoomPresenter, + dispatchers = testCoroutineDispatchers() + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -368,14 +375,18 @@ class RoomDetailsPresenterTests { val leaveRoomPresenter = FakeLeaveRoomPresenter() val notificationSettingsService = FakeNotificationSettingsService() val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) - val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + val presenter = createRoomDetailsPresenter( + room = room, + leaveRoomPresenter = leaveRoomPresenter, + notificationSettingsService = notificationSettingsService, + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { notificationSettingsService.setRoomNotificationMode(room.roomId, RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) val updatedState = consumeItemsUntilPredicate { - it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY + it.roomNotificationSettings?.mode == RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY }.last() assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) cancelAndIgnoreRemainingEvents() @@ -384,16 +395,15 @@ class RoomDetailsPresenterTests { @Test fun `present - mute room notifications`() = runTest { - val leaveRoomPresenter = FakeLeaveRoomPresenter() val notificationSettingsService = FakeNotificationSettingsService(initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY) val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) - val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { awaitItem().eventSink(RoomDetailsEvent.MuteNotification) val updatedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { - it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE + it.roomNotificationSettings?.mode == RoomNotificationMode.MUTE }.last() assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.MUTE) cancelAndIgnoreRemainingEvents() @@ -402,19 +412,18 @@ class RoomDetailsPresenterTests { @Test fun `present - unmute room notifications`() = runTest { - val leaveRoomPresenter = FakeLeaveRoomPresenter() val notificationSettingsService = FakeNotificationSettingsService( initialRoomMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY, initialEncryptedGroupDefaultMode = RoomNotificationMode.ALL_MESSAGES ) val room = aMatrixRoom(notificationSettingsService = notificationSettingsService) - val presenter = createRoomDetailsPresenter(room, leaveRoomPresenter, testCoroutineDispatchers(), notificationSettingsService) + val presenter = createRoomDetailsPresenter(room = room, notificationSettingsService = notificationSettingsService) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { awaitItem().eventSink(RoomDetailsEvent.UnmuteNotification) val updatedState = consumeItemsUntilPredicate { - it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES + it.roomNotificationSettings?.mode == RoomNotificationMode.ALL_MESSAGES }.last() assertThat(updatedState.roomNotificationSettings?.mode).isEqualTo(RoomNotificationMode.ALL_MESSAGES) cancelAndIgnoreRemainingEvents() diff --git a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt index a249e38823..1fb5cc15db 100644 --- a/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt +++ b/features/roomdetails/impl/src/test/kotlin/io/element/android/features/roomdetails/members/details/RoomMemberDetailsPresenterTests.kt @@ -20,13 +20,22 @@ import app.cash.molecule.RecompositionMode import app.cash.molecule.moleculeFlow import app.cash.turbine.test import com.google.common.truth.Truth +import io.element.android.features.createroom.api.StartDMAction +import io.element.android.features.createroom.test.FakeStartDMAction import io.element.android.features.roomdetails.aMatrixRoom import io.element.android.features.roomdetails.impl.members.aRoomMember import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState import io.element.android.libraries.architecture.Async +import io.element.android.libraries.matrix.api.MatrixClient +import io.element.android.libraries.matrix.api.core.RoomId +import io.element.android.libraries.matrix.api.room.MatrixRoom import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState +import io.element.android.libraries.matrix.api.room.RoomMember +import io.element.android.libraries.matrix.api.room.StartDMResult +import io.element.android.libraries.matrix.test.A_FAILURE_REASON +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 import io.element.android.tests.testutils.WarmUpRule @@ -49,7 +58,10 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.success("A custom avatar")) givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter( + room = room, + roomMember = roomMember + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -73,7 +85,10 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.failure(Throwable())) givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter( + room = room, + roomMember = roomMember + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -93,7 +108,10 @@ class RoomMemberDetailsPresenterTests { givenUserAvatarUrlResult(Result.success(null)) givenRoomMembersState(MatrixRoomMembersState.Ready(listOf(roomMember))) } - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter( + room = room, + roomMember = roomMember + ) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -107,9 +125,7 @@ class RoomMemberDetailsPresenterTests { @Test fun `present - BlockUser needing confirmation displays confirmation dialog`() = runTest { - val room = aMatrixRoom() - val roomMember = aRoomMember() - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -128,9 +144,7 @@ class RoomMemberDetailsPresenterTests { @Test fun `present - BlockUser and UnblockUser without confirmation change the 'blocked' state`() = runTest { - val room = aMatrixRoom() - val roomMember = aRoomMember() - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -147,11 +161,9 @@ class RoomMemberDetailsPresenterTests { @Test fun `present - BlockUser with error`() = runTest { - val room = aMatrixRoom() - val roomMember = aRoomMember() val matrixClient = FakeMatrixClient() matrixClient.givenIgnoreUserResult(Result.failure(A_THROWABLE)) - val presenter = RoomMemberDetailsPresenter(matrixClient, room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter(client = matrixClient) moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -168,9 +180,7 @@ class RoomMemberDetailsPresenterTests { @Test fun `present - UnblockUser needing confirmation displays confirmation dialog`() = runTest { - val room = aMatrixRoom() - val roomMember = aRoomMember() - val presenter = RoomMemberDetailsPresenter(FakeMatrixClient(), room, roomMember.userId) + val presenter = createRoomMemberDetailsPresenter() moleculeFlow(RecompositionMode.Immediate) { presenter.present() }.test { @@ -186,4 +196,52 @@ class RoomMemberDetailsPresenterTests { ensureAllEventsConsumed() } } + + @Test + fun `present - start DM action complete scenario`() = runTest { + val startDMAction = FakeStartDMAction() + val presenter = createRoomMemberDetailsPresenter(startDMAction = startDMAction) + moleculeFlow(RecompositionMode.Immediate) { + presenter.present() + }.test { + val initialState = awaitItem() + Truth.assertThat(initialState.startDmActionState).isInstanceOf(Async.Uninitialized::class.java) + val startDMSuccessResult = Async.Success(A_ROOM_ID) + val startDMFailureResult = Async.Failure(StartDMResult.Failure(A_FAILURE_REASON)) + + // Failure + startDMAction.givenExecuteResult(startDMFailureResult) + initialState.eventSink(RoomMemberDetailsEvents.StartDM) + Truth.assertThat(awaitItem().startDmActionState).isInstanceOf(Async.Loading::class.java) + awaitItem().also { state -> + Truth.assertThat(state.startDmActionState).isEqualTo(startDMFailureResult) + state.eventSink(RoomMemberDetailsEvents.ClearStartDMState) + } + + // Success + startDMAction.givenExecuteResult(startDMSuccessResult) + awaitItem().also { state -> + Truth.assertThat(state.startDmActionState).isEqualTo(Async.Uninitialized) + state.eventSink(RoomMemberDetailsEvents.StartDM) + } + Truth.assertThat(awaitItem().startDmActionState).isInstanceOf(Async.Loading::class.java) + awaitItem().also { state -> + Truth.assertThat(state.startDmActionState).isEqualTo(startDMSuccessResult) + } + } + } + + private fun createRoomMemberDetailsPresenter( + client: MatrixClient = FakeMatrixClient(), + room: MatrixRoom = aMatrixRoom(), + roomMember: RoomMember = aRoomMember(), + startDMAction: StartDMAction = FakeStartDMAction() + ): RoomMemberDetailsPresenter { + return RoomMemberDetailsPresenter( + roomMemberId = roomMember.userId, + client = client, + room = room, + startDMAction = startDMAction + ) + } } diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt index f9ac0d84a5..dc452f1514 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/MatrixClient.kt @@ -41,7 +41,7 @@ interface MatrixClient : Closeable { val roomListService: RoomListService val mediaLoader: MatrixMediaLoader suspend fun getRoom(roomId: RoomId): MatrixRoom? - suspend fun findDM(userId: UserId): MatrixRoom? + suspend fun findDM(userId: UserId): RoomId? suspend fun ignoreUser(userId: UserId): Result suspend fun unignoreUser(userId: UserId): Result suspend fun createRoom(createRoomParams: CreateRoomParameters): Result diff --git a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt index 722f598d51..b30248170f 100644 --- a/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt +++ b/libraries/matrix/api/src/main/kotlin/io/element/android/libraries/matrix/api/room/StartDM.kt @@ -24,11 +24,9 @@ import io.element.android.libraries.matrix.api.core.UserId * Try to find an existing DM with the given user, or create one if none exists. */ suspend fun MatrixClient.startDM(userId: UserId): StartDMResult { - val existingRoomId = findDM(userId)?.use { existingDM -> - existingDM.roomId - } - return if (existingRoomId != null) { - StartDMResult.Success(existingRoomId, isNew = false) + val existingDM = findDM(userId) + return if (existingDM != null) { + StartDMResult.Success(existingDM, isNew = false) } else { createDM(userId).fold( { StartDMResult.Success(it, isNew = true) }, diff --git a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt index ef08f32edf..3a7adefe41 100644 --- a/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt +++ b/libraries/matrix/impl/src/main/kotlin/io/element/android/libraries/matrix/impl/RustMatrixClient.kt @@ -240,9 +240,8 @@ class RustMatrixClient constructor( } } - override suspend fun findDM(userId: UserId): MatrixRoom? { - val roomId = client.getDmRoom(userId.value)?.use { RoomId(it.id()) } - return roomId?.let { getRoom(it) } + override suspend fun findDM(userId: UserId): RoomId? { + return client.getDmRoom(userId.value)?.use { RoomId(it.id()) } } override suspend fun ignoreUser(userId: UserId): Result = withContext(sessionDispatcher) { diff --git a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt index e8228e806e..3977fbb133 100644 --- a/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt +++ b/libraries/matrix/test/src/main/kotlin/io/element/android/libraries/matrix/test/FakeMatrixClient.kt @@ -39,7 +39,6 @@ import io.element.android.libraries.matrix.test.media.FakeMediaLoader import io.element.android.libraries.matrix.test.notification.FakeNotificationService import io.element.android.libraries.matrix.test.notificationsettings.FakeNotificationSettingsService import io.element.android.libraries.matrix.test.pushers.FakePushersService -import io.element.android.libraries.matrix.test.room.FakeMatrixRoom import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService import io.element.android.libraries.matrix.test.sync.FakeSyncService import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService @@ -72,8 +71,7 @@ class FakeMatrixClient( private var unignoreUserResult: Result = Result.success(Unit) private var createRoomResult: Result = Result.success(A_ROOM_ID) private var createDmResult: Result = Result.success(A_ROOM_ID) - private var createDmFailure: Throwable? = null - private var findDmResult: MatrixRoom? = FakeMatrixRoom() + private var findDmResult: RoomId? = A_ROOM_ID private var logoutFailure: Throwable? = null private val getRoomResults = mutableMapOf() private val searchUserResults = mutableMapOf>() @@ -87,7 +85,7 @@ class FakeMatrixClient( return getRoomResults[roomId] } - override suspend fun findDM(userId: UserId): MatrixRoom? { + override suspend fun findDM(userId: UserId): RoomId? { return findDmResult } @@ -99,14 +97,11 @@ class FakeMatrixClient( return unignoreUserResult } - override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result { - delay(100) + override suspend fun createRoom(createRoomParams: CreateRoomParameters): Result = simulateLongTask { return createRoomResult } - override suspend fun createDM(userId: UserId): Result { - delay(100) - createDmFailure?.let { throw it } + override suspend fun createDM(userId: UserId): Result = simulateLongTask { return createDmResult } @@ -206,11 +201,7 @@ class FakeMatrixClient( unignoreUserResult = result } - fun givenCreateDmError(failure: Throwable?) { - createDmFailure = failure - } - - fun givenFindDmResult(result: MatrixRoom?) { + fun givenFindDmResult(result: RoomId?) { findDmResult = result } diff --git a/settings.gradle.kts b/settings.gradle.kts index 0fe8ca31ee..cd3689abe3 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -82,9 +82,9 @@ includeProjects(File(rootDir, "services"), ":services") // Uncomment to include the compound-android module as a local dependency so you can work on it locally. // You will also need to clone it in the specified folder. -//includeBuild("checkouts/compound-android") { +// includeBuild("checkouts/compound-android") { // dependencySubstitution { // // substitute remote dependency with local module // substitute(module("io.element.android:compound-android")).using(project(":compound")) // } -//} +// } diff --git a/tools/localazy/config.json b/tools/localazy/config.json index 06805d49cc..88eae496a4 100644 --- a/tools/localazy/config.json +++ b/tools/localazy/config.json @@ -115,7 +115,8 @@ "screen_room_member_list_.*", "screen_dm_details_.*", "screen_room_notification_settings_.*", - "screen_notification_settings_edit_failed_updating_default_mode" + "screen_notification_settings_edit_failed_updating_default_mode", + "screen_start_chat_error_starting_chat" ] }, {