Merge pull request #2413 from element-hq/feature/bma/unitTests
Add more unit tests
This commit is contained in:
commit
354e82d489
18 changed files with 463 additions and 92 deletions
|
|
@ -22,6 +22,11 @@ plugins {
|
|||
|
||||
android {
|
||||
namespace = "io.element.android.features.location.impl"
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
|
|
@ -51,11 +56,14 @@ dependencies {
|
|||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(libs.test.truth)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.testtags)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.features.messages.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -24,78 +24,47 @@ private const val APP_NAME = "ApplicationName"
|
|||
class ShowLocationStateProvider : PreviewParameterProvider<ShowLocationState> {
|
||||
override val values: Sequence<ShowLocationState>
|
||||
get() = sequenceOf(
|
||||
ShowLocationState(
|
||||
ShowLocationState.Dialog.None,
|
||||
Location(1.23, 2.34, 4f),
|
||||
description = null,
|
||||
hasLocationPermission = false,
|
||||
isTrackMyLocation = false,
|
||||
appName = APP_NAME,
|
||||
eventSink = {},
|
||||
aShowLocationState(),
|
||||
aShowLocationState(
|
||||
permissionDialog = ShowLocationState.Dialog.PermissionDenied,
|
||||
),
|
||||
ShowLocationState(
|
||||
ShowLocationState.Dialog.PermissionDenied,
|
||||
Location(1.23, 2.34, 4f),
|
||||
description = null,
|
||||
hasLocationPermission = false,
|
||||
isTrackMyLocation = false,
|
||||
appName = APP_NAME,
|
||||
eventSink = {},
|
||||
aShowLocationState(
|
||||
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||
),
|
||||
ShowLocationState(
|
||||
ShowLocationState.Dialog.PermissionRationale,
|
||||
Location(1.23, 2.34, 4f),
|
||||
description = null,
|
||||
hasLocationPermission = false,
|
||||
isTrackMyLocation = false,
|
||||
appName = APP_NAME,
|
||||
eventSink = {},
|
||||
),
|
||||
ShowLocationState(
|
||||
ShowLocationState.Dialog.None,
|
||||
Location(1.23, 2.34, 4f),
|
||||
description = null,
|
||||
aShowLocationState(
|
||||
hasLocationPermission = true,
|
||||
isTrackMyLocation = false,
|
||||
appName = APP_NAME,
|
||||
eventSink = {},
|
||||
),
|
||||
ShowLocationState(
|
||||
ShowLocationState.Dialog.None,
|
||||
Location(1.23, 2.34, 4f),
|
||||
description = null,
|
||||
aShowLocationState(
|
||||
hasLocationPermission = true,
|
||||
isTrackMyLocation = true,
|
||||
appName = APP_NAME,
|
||||
eventSink = {},
|
||||
),
|
||||
ShowLocationState(
|
||||
ShowLocationState.Dialog.None,
|
||||
Location(1.23, 2.34, 4f),
|
||||
aShowLocationState(
|
||||
description = "My favourite place!",
|
||||
hasLocationPermission = false,
|
||||
isTrackMyLocation = false,
|
||||
appName = APP_NAME,
|
||||
eventSink = {},
|
||||
),
|
||||
ShowLocationState(
|
||||
ShowLocationState.Dialog.None,
|
||||
Location(1.23, 2.34, 4f),
|
||||
aShowLocationState(
|
||||
description = "For some reason I decided to to write a small essay that wraps at just two lines!",
|
||||
hasLocationPermission = false,
|
||||
isTrackMyLocation = false,
|
||||
appName = APP_NAME,
|
||||
eventSink = {},
|
||||
),
|
||||
ShowLocationState(
|
||||
ShowLocationState.Dialog.None,
|
||||
Location(1.23, 2.34, 4f),
|
||||
aShowLocationState(
|
||||
description = "For some reason I decided to write a small essay in the location description. " +
|
||||
"It is so long that it will wrap onto more than two lines!",
|
||||
hasLocationPermission = false,
|
||||
isTrackMyLocation = false,
|
||||
appName = APP_NAME,
|
||||
eventSink = {},
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fun aShowLocationState(
|
||||
permissionDialog: ShowLocationState.Dialog = ShowLocationState.Dialog.None,
|
||||
location: Location = Location(1.23, 2.34, 4f),
|
||||
description: String? = null,
|
||||
hasLocationPermission: Boolean = false,
|
||||
isTrackMyLocation: Boolean = false,
|
||||
appName: String = APP_NAME,
|
||||
eventSink: (ShowLocationEvents) -> Unit = {},
|
||||
) = ShowLocationState(
|
||||
permissionDialog = permissionDialog,
|
||||
location = location,
|
||||
description = description,
|
||||
hasLocationPermission = hasLocationPermission,
|
||||
isTrackMyLocation = isTrackMyLocation,
|
||||
appName = appName,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -120,10 +120,14 @@ fun ShowLocationView(
|
|||
)
|
||||
},
|
||||
navigationIcon = {
|
||||
BackButton(onClick = onBackPressed)
|
||||
BackButton(
|
||||
onClick = onBackPressed,
|
||||
)
|
||||
},
|
||||
actions = {
|
||||
IconButton(onClick = { state.eventSink(ShowLocationEvents.Share) }) {
|
||||
IconButton(
|
||||
onClick = { state.eventSink(ShowLocationEvents.Share) }
|
||||
) {
|
||||
Icon(
|
||||
imageVector = CompoundIcons.ShareAndroid(),
|
||||
contentDescription = stringResource(CommonStrings.action_share),
|
||||
|
|
|
|||
|
|
@ -0,0 +1,156 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.location.impl.show
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.ui.platform.LocalInspectionMode
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithTag
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class ShowLocationViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `test back action`() {
|
||||
val eventsRecorder = EventsRecorder<ShowLocationEvents>(expectEvents = false)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setShowLocationView(
|
||||
state = aShowLocationState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = callback,
|
||||
)
|
||||
rule.pressBack()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test share action`() {
|
||||
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||
rule.setShowLocationView(
|
||||
aShowLocationState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
)
|
||||
val shareContentDescription = rule.activity.getString(CommonStrings.action_share)
|
||||
rule.onNodeWithContentDescription(shareContentDescription).performClick()
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.Share)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `test fab click`() {
|
||||
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||
rule.setShowLocationView(
|
||||
aShowLocationState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
)
|
||||
val shareContentDescription = rule.activity.getString(CommonStrings.action_share)
|
||||
rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick()
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.TrackMyLocation(true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when permission denied is displayed user can open the settings`() {
|
||||
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||
rule.setShowLocationView(
|
||||
aShowLocationState(
|
||||
permissionDialog = ShowLocationState.Dialog.PermissionDenied,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.OpenAppSettings)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when permission denied is displayed user can close the dialog`() {
|
||||
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||
rule.setShowLocationView(
|
||||
aShowLocationState(
|
||||
permissionDialog = ShowLocationState.Dialog.PermissionDenied,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.DismissDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when permission rationale is displayed user can request permissions`() {
|
||||
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||
rule.setShowLocationView(
|
||||
aShowLocationState(
|
||||
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_continue)
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.RequestPermissions)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when permission rationale is displayed user can close the dialog`() {
|
||||
val eventsRecorder = EventsRecorder<ShowLocationEvents>()
|
||||
rule.setShowLocationView(
|
||||
aShowLocationState(
|
||||
permissionDialog = ShowLocationState.Dialog.PermissionRationale,
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackPressed = EnsureNeverCalled(),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(ShowLocationEvents.DismissDialog)
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setShowLocationView(
|
||||
state: ShowLocationState,
|
||||
onBackPressed: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
setContent {
|
||||
// Simulate a LocalInspectionMode for MapboxMap
|
||||
CompositionLocalProvider(LocalInspectionMode provides true) {
|
||||
ShowLocationView(
|
||||
state = state,
|
||||
onBackPressed = onBackPressed,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -49,6 +49,7 @@ import io.element.android.features.messages.impl.voicemessages.timeline.FakeReda
|
|||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.features.poll.api.actions.EndPollAction
|
||||
import io.element.android.features.poll.test.actions.FakeEndPollAction
|
||||
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
|
||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||
|
|
@ -98,6 +99,7 @@ import io.element.android.tests.testutils.testCoroutineDispatchers
|
|||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -372,6 +374,22 @@ class MessagesPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action end poll`() = runTest {
|
||||
val endPollAction = FakeEndPollAction()
|
||||
val presenter = createMessagesPresenter(endPollAction = endPollAction)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
endPollAction.verifyExecutionCount(0)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
|
||||
delay(1)
|
||||
endPollAction.verifyExecutionCount(1)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action redact`() = runTest {
|
||||
val coroutineDispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true)
|
||||
|
|
@ -683,6 +701,7 @@ class MessagesPresenterTest {
|
|||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||
endPollAction: EndPollAction = FakeEndPollAction(),
|
||||
): MessagesPresenter {
|
||||
val mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom)
|
||||
val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter)
|
||||
|
|
@ -721,7 +740,7 @@ class MessagesPresenterTest {
|
|||
encryptionService = FakeEncryptionService(),
|
||||
verificationService = FakeSessionVerificationService(),
|
||||
redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
||||
endPollAction = FakeEndPollAction(),
|
||||
endPollAction = endPollAction,
|
||||
sendPollResponseAction = FakeSendPollResponseAction(),
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -87,10 +87,13 @@ import kotlinx.coroutines.test.advanceUntilIdle
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.RobolectricTestRunner
|
||||
import uniffi.wysiwyg_composer.MentionsState
|
||||
import java.io.File
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@RunWith(RobolectricTestRunner::class)
|
||||
class MessageComposerPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
|
@ -875,6 +878,19 @@ class MessageComposerPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - send uri`() = runTest {
|
||||
val presenter = createPresenter(this)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
val state = presenter.present()
|
||||
remember(state, state.richTextEditorState.messageHtml) { state }
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink.invoke(MessageComposerEvents.SendUri(Uri.parse("content://uri")))
|
||||
waitForPredicate { mediaPreProcessor.processCallCount == 1 }
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle typing notice event`() = runTest {
|
||||
val room = FakeMatrixRoom()
|
||||
|
|
|
|||
|
|
@ -23,6 +23,11 @@ plugins {
|
|||
|
||||
android {
|
||||
namespace = "io.element.android.features.roomdetails.impl"
|
||||
testOptions {
|
||||
unitTests {
|
||||
isIncludeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
anvil {
|
||||
|
|
@ -61,6 +66,7 @@ dependencies {
|
|||
testImplementation(libs.test.truth)
|
||||
testImplementation(libs.test.turbine)
|
||||
testImplementation(libs.test.mockk)
|
||||
testImplementation(libs.test.robolectric)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
testImplementation(projects.libraries.mediaupload.test)
|
||||
testImplementation(projects.libraries.mediapickers.test)
|
||||
|
|
@ -70,6 +76,8 @@ dependencies {
|
|||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.features.leaveroom.test)
|
||||
testImplementation(projects.features.createroom.test)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
testReleaseImplementation(libs.androidx.compose.ui.test.manifest)
|
||||
|
||||
ksp(libs.showkase.processor)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -55,7 +55,10 @@ fun BlockUserDialogs(state: RoomMemberDetailsState) {
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun BlockConfirmationDialog(onBlockAction: () -> Unit, onDismiss: () -> Unit) {
|
||||
private fun BlockConfirmationDialog(
|
||||
onBlockAction: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(R.string.screen_dm_details_block_user),
|
||||
content = stringResource(R.string.screen_dm_details_block_alert_description),
|
||||
|
|
@ -66,7 +69,10 @@ private fun BlockConfirmationDialog(onBlockAction: () -> Unit, onDismiss: () ->
|
|||
}
|
||||
|
||||
@Composable
|
||||
private fun UnblockConfirmationDialog(onUnblockAction: () -> Unit, onDismiss: () -> Unit) {
|
||||
private fun UnblockConfirmationDialog(
|
||||
onUnblockAction: () -> Unit,
|
||||
onDismiss: () -> Unit,
|
||||
) {
|
||||
ConfirmationDialog(
|
||||
title = stringResource(R.string.screen_dm_details_unblock_user),
|
||||
content = stringResource(R.string.screen_dm_details_unblock_alert_description),
|
||||
|
|
|
|||
|
|
@ -19,28 +19,38 @@ package io.element.android.features.roomdetails.impl.members.details
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
||||
open class RoomMemberDetailsStateProvider : PreviewParameterProvider<RoomMemberDetailsState> {
|
||||
override val values: Sequence<RoomMemberDetailsState>
|
||||
get() = sequenceOf(
|
||||
aRoomMemberDetailsState(),
|
||||
aRoomMemberDetailsState().copy(userName = null),
|
||||
aRoomMemberDetailsState().copy(isBlocked = AsyncData.Success(true)),
|
||||
aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block),
|
||||
aRoomMemberDetailsState().copy(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock),
|
||||
aRoomMemberDetailsState().copy(isBlocked = AsyncData.Loading(true)),
|
||||
aRoomMemberDetailsState().copy(startDmActionState = AsyncAction.Loading),
|
||||
aRoomMemberDetailsState(userName = null),
|
||||
aRoomMemberDetailsState(isBlocked = AsyncData.Success(true)),
|
||||
aRoomMemberDetailsState(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block),
|
||||
aRoomMemberDetailsState(displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock),
|
||||
aRoomMemberDetailsState(isBlocked = AsyncData.Loading(true)),
|
||||
aRoomMemberDetailsState(startDmActionState = AsyncAction.Loading),
|
||||
// Add other states here
|
||||
)
|
||||
}
|
||||
|
||||
fun aRoomMemberDetailsState() = RoomMemberDetailsState(
|
||||
userId = "@daniel:domain.com",
|
||||
userName = "Daniel",
|
||||
avatarUrl = null,
|
||||
isBlocked = AsyncData.Success(false),
|
||||
startDmActionState = AsyncAction.Uninitialized,
|
||||
displayConfirmationDialog = null,
|
||||
isCurrentUser = false,
|
||||
eventSink = {},
|
||||
fun aRoomMemberDetailsState(
|
||||
userId: String = "@daniel:domain.com",
|
||||
userName: String? = "Daniel",
|
||||
avatarUrl: String? = null,
|
||||
isBlocked: AsyncData<Boolean> = AsyncData.Success(false),
|
||||
startDmActionState: AsyncAction<RoomId> = AsyncAction.Uninitialized,
|
||||
displayConfirmationDialog: RoomMemberDetailsState.ConfirmationDialog? = null,
|
||||
isCurrentUser: Boolean = false,
|
||||
eventSink: (RoomMemberDetailsEvents) -> Unit = {},
|
||||
) = RoomMemberDetailsState(
|
||||
userId = userId,
|
||||
userName = userName,
|
||||
avatarUrl = avatarUrl,
|
||||
isBlocked = isBlocked,
|
||||
startDmActionState = startDmActionState,
|
||||
displayConfirmationDialog = displayConfirmationDialog,
|
||||
isCurrentUser = isCurrentUser,
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -0,0 +1,96 @@
|
|||
/*
|
||||
* Copyright (c) 2024 New Vector Ltd
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.blockuser
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.features.roomdetails.impl.R
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsEvents
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsState
|
||||
import io.element.android.features.roomdetails.impl.members.details.aRoomMemberDetailsState
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class BlockUserDialogsTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `confirm block user emit expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||
rule.setContent {
|
||||
BlockUserDialogs(
|
||||
state = aRoomMemberDetailsState(
|
||||
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_dm_details_block_alert_action)
|
||||
eventsRecorder.assertSingle(RoomMemberDetailsEvents.BlockUser(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cancel block user emit expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||
rule.setContent {
|
||||
BlockUserDialogs(
|
||||
state = aRoomMemberDetailsState(
|
||||
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Block,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(RoomMemberDetailsEvents.ClearConfirmationDialog)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `confirm unblock user emit expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||
rule.setContent {
|
||||
BlockUserDialogs(
|
||||
state = aRoomMemberDetailsState(
|
||||
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
}
|
||||
rule.clickOn(R.string.screen_dm_details_unblock_alert_action)
|
||||
eventsRecorder.assertSingle(RoomMemberDetailsEvents.UnblockUser(false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cancel unblock user emit expected Event`() {
|
||||
val eventsRecorder = EventsRecorder<RoomMemberDetailsEvents>()
|
||||
rule.setContent {
|
||||
BlockUserDialogs(
|
||||
state = aRoomMemberDetailsState(
|
||||
displayConfirmationDialog = RoomMemberDetailsState.ConfirmationDialog.Unblock,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
)
|
||||
}
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertSingle(RoomMemberDetailsEvents.ClearConfirmationDialog)
|
||||
}
|
||||
}
|
||||
|
|
@ -75,13 +75,14 @@ class RoomListPresenter @Inject constructor(
|
|||
private val inviteStateDataSource: InviteStateDataSource,
|
||||
private val leaveRoomPresenter: LeaveRoomPresenter,
|
||||
private val roomListDataSource: RoomListDataSource,
|
||||
private val encryptionService: EncryptionService,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val indicatorService: IndicatorService,
|
||||
private val searchPresenter: Presenter<RoomListSearchState>,
|
||||
private val migrationScreenPresenter: MigrationScreenPresenter,
|
||||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
) : Presenter<RoomListState> {
|
||||
private val encryptionService: EncryptionService = client.encryptionService()
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomListState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
|
|
@ -139,7 +140,6 @@ class RoomListPresenter @Inject constructor(
|
|||
contextMenu.value = RoomListState.ContextMenu.Hidden
|
||||
}
|
||||
is RoomListEvents.LeaveRoom -> leaveRoomState.eventSink(LeaveRoomEvent.ShowConfirmation(event.roomId))
|
||||
|
||||
is RoomListEvents.SetRoomIsFavorite -> coroutineScope.launch {
|
||||
client.getRoom(event.roomId)?.use { room ->
|
||||
room.setIsFavorite(event.isFavorite)
|
||||
|
|
|
|||
|
|
@ -38,9 +38,10 @@ fun aRoomListSearchState(
|
|||
isSearchActive: Boolean = false,
|
||||
query: String = "",
|
||||
results: ImmutableList<RoomListRoomSummary> = persistentListOf(),
|
||||
eventSink: (RoomListSearchEvents) -> Unit = { },
|
||||
) = RoomListSearchState(
|
||||
isSearchActive = isSearchActive,
|
||||
query = query,
|
||||
results = results,
|
||||
eventSink = { },
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ import io.element.android.features.roomlist.impl.datasource.RoomListRoomSummaryF
|
|||
import io.element.android.features.roomlist.impl.migration.InMemoryMigrationScreenStore
|
||||
import io.element.android.features.roomlist.impl.migration.MigrationScreenPresenter
|
||||
import io.element.android.features.roomlist.impl.model.createRoomListRoomSummary
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchEvents
|
||||
import io.element.android.features.roomlist.impl.search.RoomListSearchState
|
||||
import io.element.android.features.roomlist.impl.search.aRoomListSearchState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
|
@ -42,13 +43,14 @@ import io.element.android.libraries.dateformatter.test.FakeLastMessageTimestampF
|
|||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
||||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.featureflag.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
import io.element.android.libraries.matrix.api.encryption.RecoveryState
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.api.roomlist.RoomListService
|
||||
import io.element.android.libraries.matrix.api.timeline.ReceiptType
|
||||
|
|
@ -68,6 +70,7 @@ import io.element.android.libraries.matrix.test.room.aRoomInfo
|
|||
import io.element.android.libraries.matrix.test.room.aRoomSummaryFilled
|
||||
import io.element.android.libraries.matrix.test.roomlist.FakeRoomListService
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
|
|
@ -108,9 +111,12 @@ class RoomListPresenterTests {
|
|||
fun `present - show avatar indicator`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
encryptionService = encryptionService,
|
||||
)
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val presenter = createRoomListPresenter(
|
||||
encryptionService = encryptionService,
|
||||
client = matrixClient,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
coroutineScope = scope
|
||||
)
|
||||
|
|
@ -255,6 +261,33 @@ class RoomListPresenterTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle DismissRecoveryKeyPrompt`() = runTest {
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
encryptionService = encryptionService,
|
||||
)
|
||||
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.displayRecoveryKeyPrompt).isFalse()
|
||||
encryptionService.emitRecoveryState(RecoveryState.INCOMPLETE)
|
||||
val nextState = awaitItem()
|
||||
assertThat(nextState.displayRecoveryKeyPrompt).isTrue()
|
||||
nextState.eventSink(RoomListEvents.DismissRecoveryKeyPrompt)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.displayRecoveryKeyPrompt).isFalse()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - sets invite state`() = runTest {
|
||||
val inviteStateFlow = MutableStateFlow(InvitesState.NoInvites)
|
||||
|
|
@ -383,6 +416,40 @@ class RoomListPresenterTests {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - toggle search menu`() = runTest {
|
||||
val eventRecorder = EventsRecorder<RoomListSearchEvents>()
|
||||
val searchPresenter: Presenter<RoomListSearchState> = Presenter {
|
||||
aRoomListSearchState(
|
||||
eventSink = eventRecorder
|
||||
)
|
||||
}
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(
|
||||
coroutineScope = scope,
|
||||
searchPresenter = searchPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
eventRecorder.assertEmpty()
|
||||
initialState.eventSink(RoomListEvents.ToggleSearchResults)
|
||||
eventRecorder.assertSingle(
|
||||
RoomListSearchEvents.ToggleSearchVisibility
|
||||
)
|
||||
initialState.eventSink(RoomListEvents.ToggleSearchResults)
|
||||
eventRecorder.assertList(
|
||||
listOf(
|
||||
RoomListSearchEvents.ToggleSearchVisibility,
|
||||
RoomListSearchEvents.ToggleSearchVisibility
|
||||
)
|
||||
)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - change in notification settings updates the summary for decorations`() = runTest {
|
||||
val userDefinedMode = RoomNotificationMode.MENTIONS_AND_KEYWORDS_ONLY
|
||||
|
|
@ -506,8 +573,8 @@ class RoomListPresenterTests {
|
|||
givenFormat(A_FORMATTED_DATE)
|
||||
},
|
||||
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
||||
coroutineScope: CoroutineScope,
|
||||
migrationScreenPresenter: MigrationScreenPresenter = MigrationScreenPresenter(
|
||||
matrixClient = client,
|
||||
|
|
@ -531,12 +598,11 @@ class RoomListPresenterTests {
|
|||
notificationSettingsService = client.notificationSettingsService(),
|
||||
appScope = coroutineScope
|
||||
),
|
||||
encryptionService = encryptionService,
|
||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
||||
featureFlagService = featureFlagService,
|
||||
indicatorService = DefaultIndicatorService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
encryptionService = encryptionService,
|
||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.SecureStorage.key to true)),
|
||||
encryptionService = client.encryptionService(),
|
||||
featureFlagService = featureFlagService,
|
||||
),
|
||||
migrationScreenPresenter = migrationScreenPresenter,
|
||||
searchPresenter = searchPresenter,
|
||||
|
|
|
|||
|
|
@ -33,6 +33,8 @@ import androidx.compose.ui.unit.dp
|
|||
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||
import io.element.android.libraries.designsystem.preview.ElementThemedPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewGroup
|
||||
import io.element.android.libraries.testtags.TestTags
|
||||
import io.element.android.libraries.testtags.testTag
|
||||
|
||||
@Composable
|
||||
fun FloatingActionButton(
|
||||
|
|
@ -48,7 +50,7 @@ fun FloatingActionButton(
|
|||
) {
|
||||
androidx.compose.material3.FloatingActionButton(
|
||||
onClick = onClick,
|
||||
modifier = modifier,
|
||||
modifier = modifier.testTag(TestTags.floatingActionButton),
|
||||
shape = shape,
|
||||
containerColor = containerColor,
|
||||
contentColor = contentColor,
|
||||
|
|
|
|||
|
|
@ -103,6 +103,10 @@ class FakeEncryptionService : EncryptionService {
|
|||
backupStateStateFlow.emit(state)
|
||||
}
|
||||
|
||||
suspend fun emitRecoveryState(state: RecoveryState) {
|
||||
recoveryStateStateFlow.emit(state)
|
||||
}
|
||||
|
||||
suspend fun emitEnableRecoveryProgress(state: EnableRecoveryProgress) {
|
||||
enableRecoveryProgressStateFlow.emit(state)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -75,6 +75,11 @@ object TestTags {
|
|||
val dialogNegative = TestTag("dialog-negative")
|
||||
val dialogNeutral = TestTag("dialog-neutral")
|
||||
|
||||
/**
|
||||
* Floating Action Button.
|
||||
*/
|
||||
val floatingActionButton = TestTag("floating-action-button")
|
||||
|
||||
/**
|
||||
* Timeline item.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -85,6 +85,8 @@ fun Project.setupKover() {
|
|||
"*Presenter\$present\$*",
|
||||
// Forked from compose
|
||||
"io.element.android.libraries.designsystem.theme.components.bottomsheet.*",
|
||||
// Test presenter
|
||||
"io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter",
|
||||
)
|
||||
annotatedBy(
|
||||
"androidx.compose.ui.tooling.preview.Preview",
|
||||
|
|
|
|||
|
|
@ -101,7 +101,6 @@ class RoomListScreen(
|
|||
notificationSettingsService = matrixClient.notificationSettingsService(),
|
||||
appScope = Singleton.appScope
|
||||
),
|
||||
encryptionService = encryptionService,
|
||||
indicatorService = DefaultIndicatorService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
encryptionService = encryptionService,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue