Merge branch 'develop' into feature/valere/call/decline_timeline_rendering
This commit is contained in:
commit
4c761812c6
171 changed files with 2932 additions and 3196 deletions
3
.idea/kotlinc.xml
generated
3
.idea/kotlinc.xml
generated
|
|
@ -1,6 +1,7 @@
|
||||||
<?xml version="1.0" encoding="UTF-8"?>
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
<project version="4">
|
<project version="4">
|
||||||
<component name="KotlinJpsPluginSettings">
|
<component name="KotlinJpsPluginSettings">
|
||||||
<option name="version" value="2.3.20" />
|
<option name="externalSystemId" value="Gradle" />
|
||||||
|
<option name="version" value="2.3.21" />
|
||||||
</component>
|
</component>
|
||||||
</project>
|
</project>
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,8 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(libs.coroutines.core)
|
||||||
implementation(libs.androidx.annotationjvm)
|
implementation(libs.androidx.annotationjvm)
|
||||||
|
implementation(libs.androidx.corektx)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@ dependencies {
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
implementation(projects.libraries.matrixmedia.api)
|
implementation(projects.libraries.matrixmedia.api)
|
||||||
|
implementation(projects.libraries.sessionStorage.api)
|
||||||
implementation(projects.libraries.uiCommon)
|
implementation(projects.libraries.uiCommon)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.features.login.api)
|
implementation(projects.features.login.api)
|
||||||
|
|
|
||||||
|
|
@ -46,7 +46,7 @@ allprojects {
|
||||||
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
config.from(files("$rootDir/tools/detekt/detekt.yml"))
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
detektPlugins("io.nlopez.compose.rules:detekt:0.5.7")
|
detektPlugins("io.nlopez.compose.rules:detekt:0.5.8")
|
||||||
detektPlugins(project(":tests:detekt-rules"))
|
detektPlugins(project(":tests:detekt-rules"))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.announcement.impl.fullscreen
|
package io.element.android.features.announcement.impl.fullscreen
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.announcement.api.Announcement
|
import io.element.android.features.announcement.api.Announcement
|
||||||
import io.element.android.features.announcement.impl.AnnouncementEvent
|
import io.element.android.features.announcement.impl.AnnouncementEvent
|
||||||
|
|
@ -20,43 +23,39 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class FullscreenAnnouncementViewTest {
|
class FullscreenAnnouncementViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back sends a AnnouncementEvent`() {
|
fun `clicking on back sends a AnnouncementEvent`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
|
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
|
||||||
rule.setFullscreenAnnouncementView(
|
setFullscreenAnnouncementView(
|
||||||
anAnnouncementState(
|
anAnnouncementState(
|
||||||
announcement = Announcement.Fullscreen.Space,
|
announcement = Announcement.Fullscreen.Space,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Continue sends a AnnouncementEvent`() {
|
fun `clicking on Continue sends a AnnouncementEvent`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
|
val eventsRecorder = EventsRecorder<AnnouncementEvent>()
|
||||||
rule.setFullscreenAnnouncementView(
|
setFullscreenAnnouncementView(
|
||||||
anAnnouncementState(
|
anAnnouncementState(
|
||||||
announcement = Announcement.Fullscreen.Space,
|
announcement = Announcement.Fullscreen.Space,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
eventsRecorder.assertSingle(AnnouncementEvent.Continue(Announcement.Fullscreen.Space))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setFullscreenAnnouncementView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setFullscreenAnnouncementView(
|
||||||
state: AnnouncementState,
|
state: AnnouncementState,
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
|
|
|
||||||
|
|
@ -14,22 +14,9 @@ import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
import io.element.android.libraries.matrix.api.core.SessionId
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
||||||
sealed interface CallType : NodeInputs, Parcelable {
|
@Parcelize
|
||||||
@Parcelize
|
data class CallData(
|
||||||
data class ExternalUrl(val url: String) : CallType {
|
val sessionId: SessionId,
|
||||||
override fun toString(): String {
|
val roomId: RoomId,
|
||||||
return "ExternalUrl"
|
val isAudioCall: Boolean
|
||||||
}
|
) : NodeInputs, Parcelable
|
||||||
}
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
data class RoomCall(
|
|
||||||
val sessionId: SessionId,
|
|
||||||
val roomId: RoomId,
|
|
||||||
val isAudioCall: Boolean
|
|
||||||
) : CallType {
|
|
||||||
override fun toString(): String {
|
|
||||||
return "RoomCall(sessionId=$sessionId, roomId=$roomId, isAudioCall=$isAudioCall)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -17,13 +17,13 @@ import io.element.android.libraries.matrix.api.core.UserId
|
||||||
interface ElementCallEntryPoint {
|
interface ElementCallEntryPoint {
|
||||||
/**
|
/**
|
||||||
* Start a call of the given type.
|
* Start a call of the given type.
|
||||||
* @param callType The type of call to start.
|
* @param callData The data of call to start.
|
||||||
*/
|
*/
|
||||||
fun startCall(callType: CallType)
|
fun startCall(callData: CallData)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handle an incoming call.
|
* Handle an incoming call.
|
||||||
* @param callType The type of call.
|
* @param callData The data of call.
|
||||||
* @param eventId The event id of the event that started the call.
|
* @param eventId The event id of the event that started the call.
|
||||||
* @param senderId The user id of the sender of the event that started the call.
|
* @param senderId The user id of the sender of the event that started the call.
|
||||||
* @param roomName The name of the room the call is in.
|
* @param roomName The name of the room the call is in.
|
||||||
|
|
@ -35,7 +35,7 @@ interface ElementCallEntryPoint {
|
||||||
* @param textContent The text content of the notification. If null the default content from the system will be used.
|
* @param textContent The text content of the notification. If null the default content from the system will be used.
|
||||||
*/
|
*/
|
||||||
suspend fun handleIncomingCall(
|
suspend fun handleIncomingCall(
|
||||||
callType: CallType.RoomCall,
|
callData: CallData,
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
senderId: UserId,
|
senderId: UserId,
|
||||||
roomName: String?,
|
roomName: String?,
|
||||||
|
|
|
||||||
|
|
@ -30,44 +30,10 @@
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.ElementCallActivity"
|
android:name=".ui.ElementCallActivity"
|
||||||
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
|
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden|keyboard|navigation|uiMode"
|
||||||
android:exported="true"
|
|
||||||
android:label="@string/element_call"
|
android:label="@string/element_call"
|
||||||
android:launchMode="singleTask"
|
android:launchMode="singleTask"
|
||||||
android:supportsPictureInPicture="true"
|
android:supportsPictureInPicture="true"
|
||||||
android:taskAffinity="io.element.android.features.call">
|
android:taskAffinity="io.element.android.features.call" />
|
||||||
|
|
||||||
<intent-filter android:autoVerify="true">
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data android:scheme="https" />
|
|
||||||
|
|
||||||
<!-- Matching asset file: https://call.element.io/.well-known/assetlinks.json -->
|
|
||||||
<data android:host="call.element.io" />
|
|
||||||
</intent-filter>
|
|
||||||
<!-- Custom scheme to handle urls from other domains in the format: element://call?url=https%3A%2F%2Felement.io -->
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data android:scheme="element" />
|
|
||||||
<data android:host="call" />
|
|
||||||
</intent-filter>
|
|
||||||
<!-- Custom scheme to handle urls from other domains in the format: io.element.call:/?url=https%3A%2F%2Felement.io -->
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.VIEW" />
|
|
||||||
|
|
||||||
<category android:name="android.intent.category.DEFAULT" />
|
|
||||||
<category android:name="android.intent.category.BROWSABLE" />
|
|
||||||
|
|
||||||
<data android:scheme="io.element.call" />
|
|
||||||
</intent-filter>
|
|
||||||
|
|
||||||
</activity>
|
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".ui.IncomingCallActivity"
|
android:name=".ui.IncomingCallActivity"
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ package io.element.android.features.call.impl
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import dev.zacsweers.metro.AppScope
|
import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.api.ElementCallEntryPoint
|
import io.element.android.features.call.api.ElementCallEntryPoint
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||||
|
|
@ -30,12 +30,12 @@ class DefaultElementCallEntryPoint(
|
||||||
const val REQUEST_CODE = 2255
|
const val REQUEST_CODE = 2255
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun startCall(callType: CallType) {
|
override fun startCall(callData: CallData) {
|
||||||
context.startActivity(IntentProvider.createIntent(context, callType))
|
context.startActivity(IntentProvider.createIntent(context, callData))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun handleIncomingCall(
|
override suspend fun handleIncomingCall(
|
||||||
callType: CallType.RoomCall,
|
callData: CallData,
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
senderId: UserId,
|
senderId: UserId,
|
||||||
roomName: String?,
|
roomName: String?,
|
||||||
|
|
@ -47,8 +47,8 @@ class DefaultElementCallEntryPoint(
|
||||||
textContent: String?,
|
textContent: String?,
|
||||||
) {
|
) {
|
||||||
val incomingCallNotificationData = CallNotificationData(
|
val incomingCallNotificationData = CallNotificationData(
|
||||||
sessionId = callType.sessionId,
|
sessionId = callData.sessionId,
|
||||||
roomId = callType.roomId,
|
roomId = callData.roomId,
|
||||||
eventId = eventId,
|
eventId = eventId,
|
||||||
senderId = senderId,
|
senderId = senderId,
|
||||||
roomName = roomName,
|
roomName = roomName,
|
||||||
|
|
@ -58,7 +58,7 @@ class DefaultElementCallEntryPoint(
|
||||||
expirationTimestamp = expirationTimestamp,
|
expirationTimestamp = expirationTimestamp,
|
||||||
notificationChannelId = notificationChannelId,
|
notificationChannelId = notificationChannelId,
|
||||||
textContent = textContent,
|
textContent = textContent,
|
||||||
audioOnly = callType.isAudioCall
|
audioOnly = callData.isAudioCall,
|
||||||
)
|
)
|
||||||
activeCallManager.registerIncomingCall(notificationData = incomingCallNotificationData)
|
activeCallManager.registerIncomingCall(notificationData = incomingCallNotificationData)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ import androidx.core.app.PendingIntentCompat
|
||||||
import androidx.core.app.Person
|
import androidx.core.app.Person
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.appconfig.ElementCallConfig
|
import io.element.android.appconfig.ElementCallConfig
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.impl.receivers.DeclineCallBroadcastReceiver
|
import io.element.android.features.call.impl.receivers.DeclineCallBroadcastReceiver
|
||||||
import io.element.android.features.call.impl.ui.IncomingCallActivity
|
import io.element.android.features.call.impl.ui.IncomingCallActivity
|
||||||
import io.element.android.features.call.impl.utils.IntentProvider
|
import io.element.android.features.call.impl.utils.IntentProvider
|
||||||
|
|
@ -89,7 +89,14 @@ class RingingCallNotificationCreator(
|
||||||
.setImportant(true)
|
.setImportant(true)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val answerIntent = IntentProvider.getPendingIntent(context, CallType.RoomCall(sessionId, roomId, isAudioCall = audioOnly))
|
val answerIntent = IntentProvider.getPendingIntent(
|
||||||
|
context,
|
||||||
|
CallData(
|
||||||
|
sessionId = sessionId,
|
||||||
|
roomId = roomId,
|
||||||
|
isAudioCall = audioOnly,
|
||||||
|
),
|
||||||
|
)
|
||||||
val notificationData = CallNotificationData(
|
val notificationData = CallNotificationData(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
|
|
|
||||||
|
|
@ -10,8 +10,8 @@ package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
import io.element.android.features.call.impl.utils.PipController
|
import io.element.android.features.call.impl.utils.PipController
|
||||||
|
|
||||||
sealed interface PictureInPictureEvents {
|
sealed interface PictureInPictureEvent {
|
||||||
data class SetPipController(val pipController: PipController) : PictureInPictureEvents
|
data class SetPipController(val pipController: PipController) : PictureInPictureEvent
|
||||||
data object EnterPictureInPicture : PictureInPictureEvents
|
data object EnterPictureInPicture : PictureInPictureEvent
|
||||||
data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvents
|
data class OnPictureInPictureModeChanged(val isInPip: Boolean) : PictureInPictureEvent
|
||||||
}
|
}
|
||||||
|
|
@ -36,17 +36,17 @@ class PictureInPicturePresenter(
|
||||||
var isInPictureInPicture by remember { mutableStateOf(false) }
|
var isInPictureInPicture by remember { mutableStateOf(false) }
|
||||||
var pipController by remember { mutableStateOf<PipController?>(null) }
|
var pipController by remember { mutableStateOf<PipController?>(null) }
|
||||||
|
|
||||||
fun handleEvent(event: PictureInPictureEvents) {
|
fun handleEvent(event: PictureInPictureEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is PictureInPictureEvents.SetPipController -> {
|
is PictureInPictureEvent.SetPipController -> {
|
||||||
pipController = event.pipController
|
pipController = event.pipController
|
||||||
}
|
}
|
||||||
PictureInPictureEvents.EnterPictureInPicture -> {
|
PictureInPictureEvent.EnterPictureInPicture -> {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
switchToPip(pipController)
|
switchToPip(pipController)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is PictureInPictureEvents.OnPictureInPictureModeChanged -> {
|
is PictureInPictureEvent.OnPictureInPictureModeChanged -> {
|
||||||
Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: ${event.isInPip}")
|
Timber.tag(loggerTag.value).d("onPictureInPictureModeChanged: ${event.isInPip}")
|
||||||
isInPictureInPicture = event.isInPip
|
isInPictureInPicture = event.isInPip
|
||||||
if (event.isInPip) {
|
if (event.isInPip) {
|
||||||
|
|
|
||||||
|
|
@ -11,5 +11,5 @@ package io.element.android.features.call.impl.pip
|
||||||
data class PictureInPictureState(
|
data class PictureInPictureState(
|
||||||
val supportPip: Boolean,
|
val supportPip: Boolean,
|
||||||
val isInPictureInPicture: Boolean,
|
val isInPictureInPicture: Boolean,
|
||||||
val eventSink: (PictureInPictureEvents) -> Unit,
|
val eventSink: (PictureInPictureEvent) -> Unit,
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ package io.element.android.features.call.impl.pip
|
||||||
fun aPictureInPictureState(
|
fun aPictureInPictureState(
|
||||||
supportPip: Boolean = false,
|
supportPip: Boolean = false,
|
||||||
isInPictureInPicture: Boolean = false,
|
isInPictureInPicture: Boolean = false,
|
||||||
eventSink: (PictureInPictureEvents) -> Unit = {},
|
eventSink: (PictureInPictureEvent) -> Unit = {},
|
||||||
): PictureInPictureState {
|
): PictureInPictureState {
|
||||||
return PictureInPictureState(
|
return PictureInPictureState(
|
||||||
supportPip = supportPip,
|
supportPip = supportPip,
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@ import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.core.content.IntentCompat
|
import androidx.core.content.IntentCompat
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.impl.di.CallBindings
|
import io.element.android.features.call.impl.di.CallBindings
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||||
|
|
@ -42,7 +42,7 @@ class DeclineCallBroadcastReceiver : BroadcastReceiver() {
|
||||||
context.bindings<CallBindings>().inject(this)
|
context.bindings<CallBindings>().inject(this)
|
||||||
appCoroutineScope.launch {
|
appCoroutineScope.launch {
|
||||||
activeCallManager.hangUpCall(
|
activeCallManager.hangUpCall(
|
||||||
callType = CallType.RoomCall(
|
callData = CallData(
|
||||||
sessionId = notificationData.sessionId,
|
sessionId = notificationData.sessionId,
|
||||||
roomId = notificationData.roomId,
|
roomId = notificationData.roomId,
|
||||||
isAudioCall = notificationData.audioOnly
|
isAudioCall = notificationData.audioOnly
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.impl.ui
|
||||||
|
internal sealed interface CallScreenBackPressAction {
|
||||||
|
data object DispatchEscapeToWebView : CallScreenBackPressAction
|
||||||
|
data object EnterPictureInPicture : CallScreenBackPressAction
|
||||||
|
}
|
||||||
|
|
||||||
|
internal object CallScreenBackPressPolicy {
|
||||||
|
fun resolve(
|
||||||
|
supportPip: Boolean,
|
||||||
|
hasWebView: Boolean,
|
||||||
|
fromNative: Boolean,
|
||||||
|
): CallScreenBackPressAction? {
|
||||||
|
return when {
|
||||||
|
hasWebView && fromNative -> CallScreenBackPressAction.DispatchEscapeToWebView
|
||||||
|
hasWebView && supportPip -> CallScreenBackPressAction.EnterPictureInPicture
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,8 +10,8 @@ package io.element.android.features.call.impl.ui
|
||||||
|
|
||||||
import io.element.android.features.call.impl.utils.WidgetMessageInterceptor
|
import io.element.android.features.call.impl.utils.WidgetMessageInterceptor
|
||||||
|
|
||||||
sealed interface CallScreenEvents {
|
sealed interface CallScreenEvent {
|
||||||
data object Hangup : CallScreenEvents
|
data object Hangup : CallScreenEvent
|
||||||
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvents
|
data class SetupMessageChannels(val widgetMessageInterceptor: WidgetMessageInterceptor) : CallScreenEvent
|
||||||
data class OnWebViewError(val description: String?) : CallScreenEvents
|
data class OnWebViewError(val description: String?) : CallScreenEvent
|
||||||
}
|
}
|
||||||
|
|
@ -23,7 +23,7 @@ import dev.zacsweers.metro.AssistedFactory
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.impl.data.WidgetMessage
|
import io.element.android.features.call.impl.data.WidgetMessage
|
||||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||||
import io.element.android.features.call.impl.utils.CallWidgetProvider
|
import io.element.android.features.call.impl.utils.CallWidgetProvider
|
||||||
|
|
@ -52,7 +52,7 @@ import kotlin.time.Duration.Companion.seconds
|
||||||
|
|
||||||
@AssistedInject
|
@AssistedInject
|
||||||
class CallScreenPresenter(
|
class CallScreenPresenter(
|
||||||
@Assisted private val callType: CallType,
|
@Assisted private val callData: CallData,
|
||||||
@Assisted private val navigator: CallScreenNavigator,
|
@Assisted private val navigator: CallScreenNavigator,
|
||||||
private val callWidgetProvider: CallWidgetProvider,
|
private val callWidgetProvider: CallWidgetProvider,
|
||||||
userAgentProvider: UserAgentProvider,
|
userAgentProvider: UserAgentProvider,
|
||||||
|
|
@ -69,10 +69,9 @@ class CallScreenPresenter(
|
||||||
) : Presenter<CallScreenState> {
|
) : Presenter<CallScreenState> {
|
||||||
@AssistedFactory
|
@AssistedFactory
|
||||||
interface Factory {
|
interface Factory {
|
||||||
fun create(callType: CallType, navigator: CallScreenNavigator): CallScreenPresenter
|
fun create(callData: CallData, navigator: CallScreenNavigator): CallScreenPresenter
|
||||||
}
|
}
|
||||||
|
|
||||||
private val isInWidgetMode = callType is CallType.RoomCall
|
|
||||||
private val userAgent = userAgentProvider.provide()
|
private val userAgent = userAgentProvider.provide()
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
|
|
@ -90,9 +89,9 @@ class CallScreenPresenter(
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
// Sets the call as joined
|
// Sets the call as joined
|
||||||
activeCallManager.joinedCall(callType)
|
activeCallManager.joinedCall(callData)
|
||||||
fetchRoomCallUrl(
|
fetchRoomCallUrl(
|
||||||
inputs = callType,
|
callData = callData,
|
||||||
urlState = urlState,
|
urlState = urlState,
|
||||||
callWidgetDriver = callWidgetDriver,
|
callWidgetDriver = callWidgetDriver,
|
||||||
languageTag = languageTag,
|
languageTag = languageTag,
|
||||||
|
|
@ -100,19 +99,10 @@ class CallScreenPresenter(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
onDispose {
|
onDispose {
|
||||||
appCoroutineScope.launch { activeCallManager.hangUpCall(callType) }
|
appCoroutineScope.launch { activeCallManager.hangUpCall(callData) }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
screenTracker.TrackScreen(screen = MobileScreen.ScreenName.RoomCall)
|
||||||
when (callType) {
|
|
||||||
is CallType.ExternalUrl -> {
|
|
||||||
// No analytics yet for external calls
|
|
||||||
}
|
|
||||||
is CallType.RoomCall -> {
|
|
||||||
screenTracker.TrackScreen(screen = MobileScreen.ScreenName.RoomCall)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
HandleMatrixClientSyncState()
|
HandleMatrixClientSyncState()
|
||||||
|
|
||||||
callWidgetDriver.value?.let { driver ->
|
callWidgetDriver.value?.let { driver ->
|
||||||
|
|
@ -149,25 +139,22 @@ class CallScreenPresenter(
|
||||||
.launchIn(this)
|
.launchIn(this)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (callType is CallType.RoomCall) {
|
LaunchedEffect(Unit) {
|
||||||
// Note: For external calls isWidgetLoaded will always be false
|
// Wait for the call to be joined, if it takes too long, we display an error
|
||||||
LaunchedEffect(Unit) {
|
delay(10.seconds)
|
||||||
// Wait for the call to be joined, if it takes too long, we display an error
|
|
||||||
delay(10.seconds)
|
|
||||||
|
|
||||||
if (!isWidgetLoaded) {
|
if (!isWidgetLoaded) {
|
||||||
Timber.w("The call took too long to load. Displaying an error before exiting.")
|
Timber.w("The call took too long to load. Displaying an error before exiting.")
|
||||||
|
|
||||||
// This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call
|
// This will display a simple 'Sorry, an error occurred' dialog and force the user to exit the call
|
||||||
webViewError = ""
|
webViewError = ""
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun handleEvent(event: CallScreenEvents) {
|
fun handleEvent(event: CallScreenEvent) {
|
||||||
when (event) {
|
when (event) {
|
||||||
is CallScreenEvents.Hangup -> {
|
is CallScreenEvent.Hangup -> {
|
||||||
val widgetId = callWidgetDriver.value?.id
|
val widgetId = callWidgetDriver.value?.id
|
||||||
val interceptor = messageInterceptor.value
|
val interceptor = messageInterceptor.value
|
||||||
if (widgetId != null && interceptor != null && isWidgetLoaded) {
|
if (widgetId != null && interceptor != null && isWidgetLoaded) {
|
||||||
|
|
@ -187,10 +174,10 @@ class CallScreenPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is CallScreenEvents.SetupMessageChannels -> {
|
is CallScreenEvent.SetupMessageChannels -> {
|
||||||
messageInterceptor.value = event.widgetMessageInterceptor
|
messageInterceptor.value = event.widgetMessageInterceptor
|
||||||
}
|
}
|
||||||
is CallScreenEvents.OnWebViewError -> {
|
is CallScreenEvent.OnWebViewError -> {
|
||||||
if (!ignoreWebViewError) {
|
if (!ignoreWebViewError) {
|
||||||
webViewError = event.description.orEmpty()
|
webViewError = event.description.orEmpty()
|
||||||
}
|
}
|
||||||
|
|
@ -204,37 +191,29 @@ class CallScreenPresenter(
|
||||||
webViewError = webViewError,
|
webViewError = webViewError,
|
||||||
userAgent = userAgent,
|
userAgent = userAgent,
|
||||||
isCallActive = isWidgetLoaded,
|
isCallActive = isWidgetLoaded,
|
||||||
isInWidgetMode = isInWidgetMode,
|
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun fetchRoomCallUrl(
|
private suspend fun fetchRoomCallUrl(
|
||||||
inputs: CallType,
|
callData: CallData,
|
||||||
urlState: MutableState<AsyncData<String>>,
|
urlState: MutableState<AsyncData<String>>,
|
||||||
callWidgetDriver: MutableState<MatrixWidgetDriver?>,
|
callWidgetDriver: MutableState<MatrixWidgetDriver?>,
|
||||||
languageTag: String?,
|
languageTag: String?,
|
||||||
theme: String?,
|
theme: String?,
|
||||||
) {
|
) {
|
||||||
urlState.runCatchingUpdatingState {
|
urlState.runCatchingUpdatingState {
|
||||||
when (inputs) {
|
val result = callWidgetProvider.getWidget(
|
||||||
is CallType.ExternalUrl -> {
|
sessionId = callData.sessionId,
|
||||||
inputs.url
|
roomId = callData.roomId,
|
||||||
}
|
clientId = UUID.randomUUID().toString(),
|
||||||
is CallType.RoomCall -> {
|
isAudioCall = callData.isAudioCall,
|
||||||
val result = callWidgetProvider.getWidget(
|
languageTag = languageTag,
|
||||||
sessionId = inputs.sessionId,
|
theme = theme,
|
||||||
roomId = inputs.roomId,
|
).getOrThrow()
|
||||||
clientId = UUID.randomUUID().toString(),
|
callWidgetDriver.value = result.driver
|
||||||
isAudioCall = inputs.isAudioCall,
|
Timber.d("Call widget driver initialized for sessionId: ${callData.sessionId}, roomId: ${callData.roomId}")
|
||||||
languageTag = languageTag,
|
result.url
|
||||||
theme = theme,
|
|
||||||
).getOrThrow()
|
|
||||||
callWidgetDriver.value = result.driver
|
|
||||||
Timber.d("Call widget driver initialized for sessionId: ${inputs.sessionId}, roomId: ${inputs.roomId}")
|
|
||||||
result.url
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -242,12 +221,11 @@ class CallScreenPresenter(
|
||||||
private fun HandleMatrixClientSyncState() {
|
private fun HandleMatrixClientSyncState() {
|
||||||
val coroutineScope = rememberCoroutineScope()
|
val coroutineScope = rememberCoroutineScope()
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
val roomCallType = callType as? CallType.RoomCall ?: return@DisposableEffect onDispose {}
|
val client = matrixClientsProvider.getOrNull(callData.sessionId) ?: return@DisposableEffect onDispose {
|
||||||
val client = matrixClientsProvider.getOrNull(roomCallType.sessionId) ?: return@DisposableEffect onDispose {
|
Timber.w("No MatrixClient found for sessionId, can't send call notification: ${callData.sessionId}")
|
||||||
Timber.w("No MatrixClient found for sessionId, can't send call notification: ${roomCallType.sessionId}")
|
|
||||||
}
|
}
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
Timber.d("Observing sync state in-call for sessionId: ${roomCallType.sessionId}")
|
Timber.d("Observing sync state in-call for sessionId: ${callData.sessionId}")
|
||||||
client.syncService.syncState
|
client.syncService.syncState
|
||||||
.collect { state ->
|
.collect { state ->
|
||||||
if (state != SyncState.Running) {
|
if (state != SyncState.Running) {
|
||||||
|
|
@ -256,7 +234,7 @@ class CallScreenPresenter(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
onDispose {
|
onDispose {
|
||||||
Timber.d("Stopped observing sync state in-call for sessionId: ${roomCallType.sessionId}")
|
Timber.d("Stopped observing sync state in-call for sessionId: ${callData.sessionId}")
|
||||||
// Make sure we mark the call as ended in the app state
|
// Make sure we mark the call as ended in the app state
|
||||||
appForegroundStateService.updateIsInCallState(false)
|
appForegroundStateService.updateIsInCallState(false)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,5 @@ data class CallScreenState(
|
||||||
val webViewError: String?,
|
val webViewError: String?,
|
||||||
val userAgent: String,
|
val userAgent: String,
|
||||||
val isCallActive: Boolean,
|
val isCallActive: Boolean,
|
||||||
val isInWidgetMode: Boolean,
|
val eventSink: (CallScreenEvent) -> Unit,
|
||||||
val eventSink: (CallScreenEvents) -> Unit,
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -26,15 +26,13 @@ internal fun aCallScreenState(
|
||||||
webViewError: String? = null,
|
webViewError: String? = null,
|
||||||
userAgent: String = "",
|
userAgent: String = "",
|
||||||
isCallActive: Boolean = true,
|
isCallActive: Boolean = true,
|
||||||
isInWidgetMode: Boolean = false,
|
eventSink: (CallScreenEvent) -> Unit = {},
|
||||||
eventSink: (CallScreenEvents) -> Unit = {},
|
|
||||||
): CallScreenState {
|
): CallScreenState {
|
||||||
return CallScreenState(
|
return CallScreenState(
|
||||||
urlState = urlState,
|
urlState = urlState,
|
||||||
webViewError = webViewError,
|
webViewError = webViewError,
|
||||||
userAgent = userAgent,
|
userAgent = userAgent,
|
||||||
isCallActive = isCallActive,
|
isCallActive = isCallActive,
|
||||||
isInWidgetMode = isInWidgetMode,
|
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||||
import androidx.compose.ui.viewinterop.AndroidView
|
import androidx.compose.ui.viewinterop.AndroidView
|
||||||
import io.element.android.features.call.impl.R
|
import io.element.android.features.call.impl.R
|
||||||
import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
import io.element.android.features.call.impl.pip.PictureInPictureEvent
|
||||||
import io.element.android.features.call.impl.pip.PictureInPictureState
|
import io.element.android.features.call.impl.pip.PictureInPictureState
|
||||||
import io.element.android.features.call.impl.pip.aPictureInPictureState
|
import io.element.android.features.call.impl.pip.aPictureInPictureState
|
||||||
import io.element.android.features.call.impl.utils.InvalidAudioDeviceReason
|
import io.element.android.features.call.impl.utils.InvalidAudioDeviceReason
|
||||||
|
|
@ -64,11 +64,15 @@ internal fun CallScreenView(
|
||||||
requestPermissions: (Array<String>, RequestPermissionCallback) -> Unit,
|
requestPermissions: (Array<String>, RequestPermissionCallback) -> Unit,
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
fun handleBack() {
|
var callWebView by remember { mutableStateOf<WebView?>(null) }
|
||||||
if (pipState.supportPip) {
|
|
||||||
pipState.eventSink.invoke(PictureInPictureEvents.EnterPictureInPicture)
|
fun handleBack(fromNative: Boolean = false) {
|
||||||
} else {
|
when (CallScreenBackPressPolicy.resolve(supportPip = pipState.supportPip, hasWebView = callWebView != null, fromNative)) {
|
||||||
state.eventSink(CallScreenEvents.Hangup)
|
CallScreenBackPressAction.EnterPictureInPicture ->
|
||||||
|
pipState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||||
|
CallScreenBackPressAction.DispatchEscapeToWebView ->
|
||||||
|
callWebView?.dispatchEscKeyEvent()
|
||||||
|
null -> Timber.d("Back press with unsupported pip is a no-op")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -76,7 +80,7 @@ internal fun CallScreenView(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
) { padding ->
|
) { padding ->
|
||||||
BackHandler {
|
BackHandler {
|
||||||
handleBack()
|
handleBack(fromNative = true)
|
||||||
}
|
}
|
||||||
if (state.webViewError != null) {
|
if (state.webViewError != null) {
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
|
|
@ -84,7 +88,7 @@ internal fun CallScreenView(
|
||||||
append(stringResource(CommonStrings.error_unknown))
|
append(stringResource(CommonStrings.error_unknown))
|
||||||
state.webViewError.takeIf { it.isNotEmpty() }?.let { append("\n\n").append(it) }
|
state.webViewError.takeIf { it.isNotEmpty() }?.let { append("\n\n").append(it) }
|
||||||
},
|
},
|
||||||
onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
|
onSubmit = { state.eventSink(CallScreenEvent.Hangup) },
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
var webViewAudioManager by remember { mutableStateOf<WebViewAudioManager?>(null) }
|
var webViewAudioManager by remember { mutableStateOf<WebViewAudioManager?>(null) }
|
||||||
|
|
@ -111,6 +115,7 @@ internal fun CallScreenView(
|
||||||
},
|
},
|
||||||
onConsoleMessage = onConsoleMessage,
|
onConsoleMessage = onConsoleMessage,
|
||||||
onCreateWebView = { webView ->
|
onCreateWebView = { webView ->
|
||||||
|
callWebView = webView
|
||||||
webView.addBackHandler(onBackPressed = ::handleBack)
|
webView.addBackHandler(onBackPressed = ::handleBack)
|
||||||
val interceptor = WebViewWidgetMessageInterceptor(
|
val interceptor = WebViewWidgetMessageInterceptor(
|
||||||
webView = webView,
|
webView = webView,
|
||||||
|
|
@ -123,18 +128,19 @@ internal fun CallScreenView(
|
||||||
Timber.d("Can't start in-call audio mode since the app is already in it.")
|
Timber.d("Can't start in-call audio mode since the app is already in it.")
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
onError = { state.eventSink(CallScreenEvents.OnWebViewError(it)) },
|
onError = { state.eventSink(CallScreenEvent.OnWebViewError(it)) },
|
||||||
)
|
)
|
||||||
webViewAudioManager = WebViewAudioManager(
|
webViewAudioManager = WebViewAudioManager(
|
||||||
webView = webView,
|
webView = webView,
|
||||||
coroutineScope = coroutineScope,
|
coroutineScope = coroutineScope,
|
||||||
onInvalidAudioDeviceAdded = { invalidAudioDeviceReason = it },
|
onInvalidAudioDeviceAdded = { invalidAudioDeviceReason = it },
|
||||||
)
|
)
|
||||||
state.eventSink(CallScreenEvents.SetupMessageChannels(interceptor))
|
state.eventSink(CallScreenEvent.SetupMessageChannels(interceptor))
|
||||||
val pipController = WebViewPipController(webView)
|
val pipController = WebViewPipController(webView)
|
||||||
pipState.eventSink(PictureInPictureEvents.SetPipController(pipController))
|
pipState.eventSink(PictureInPictureEvent.SetPipController(pipController))
|
||||||
},
|
},
|
||||||
onDestroyWebView = {
|
onDestroyWebView = {
|
||||||
|
callWebView = null
|
||||||
// Reset audio mode
|
// Reset audio mode
|
||||||
webViewAudioManager?.onCallStopped()
|
webViewAudioManager?.onCallStopped()
|
||||||
}
|
}
|
||||||
|
|
@ -143,13 +149,15 @@ internal fun CallScreenView(
|
||||||
AsyncData.Uninitialized,
|
AsyncData.Uninitialized,
|
||||||
is AsyncData.Loading ->
|
is AsyncData.Loading ->
|
||||||
ProgressDialog(text = stringResource(id = CommonStrings.common_please_wait))
|
ProgressDialog(text = stringResource(id = CommonStrings.common_please_wait))
|
||||||
|
|
||||||
is AsyncData.Failure -> {
|
is AsyncData.Failure -> {
|
||||||
Timber.e(state.urlState.error, "WebView failed to load URL: ${state.urlState.error.message}")
|
Timber.e(state.urlState.error, "WebView failed to load URL: ${state.urlState.error.message}")
|
||||||
ErrorDialog(
|
ErrorDialog(
|
||||||
content = state.urlState.error.message.orEmpty(),
|
content = state.urlState.error.message.orEmpty(),
|
||||||
onSubmit = { state.eventSink(CallScreenEvents.Hangup) },
|
onSubmit = { state.eventSink(CallScreenEvent.Hangup) },
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
is AsyncData.Success -> Unit
|
is AsyncData.Success -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -248,15 +256,18 @@ private fun WebView.setup(
|
||||||
|
|
||||||
private fun WebView.addBackHandler(onBackPressed: () -> Unit) {
|
private fun WebView.addBackHandler(onBackPressed: () -> Unit) {
|
||||||
addJavascriptInterface(
|
addJavascriptInterface(
|
||||||
object {
|
JavascriptBackHandler {
|
||||||
@Suppress("unused")
|
onBackPressed()
|
||||||
@JavascriptInterface
|
|
||||||
fun onBackPressed() = onBackPressed()
|
|
||||||
},
|
},
|
||||||
"backHandler"
|
"backHandler"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun WebView.dispatchEscKeyEvent() {
|
||||||
|
dispatchKeyEvent(android.view.KeyEvent(android.view.KeyEvent.ACTION_DOWN, android.view.KeyEvent.KEYCODE_ESCAPE))
|
||||||
|
dispatchKeyEvent(android.view.KeyEvent(android.view.KeyEvent.ACTION_UP, android.view.KeyEvent.KEYCODE_ESCAPE))
|
||||||
|
}
|
||||||
|
|
||||||
@PreviewsDayNight
|
@PreviewsDayNight
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CallScreenViewPreview(
|
internal fun CallScreenViewPreview(
|
||||||
|
|
@ -275,3 +286,8 @@ internal fun CallScreenViewPreview(
|
||||||
internal fun InvalidAudioDeviceDialogPreview() = ElementPreview {
|
internal fun InvalidAudioDeviceDialogPreview() = ElementPreview {
|
||||||
InvalidAudioDeviceDialog(invalidAudioDeviceReason = InvalidAudioDeviceReason.BT_AUDIO_DEVICE_DISABLED) {}
|
InvalidAudioDeviceDialog(invalidAudioDeviceReason = InvalidAudioDeviceReason.BT_AUDIO_DEVICE_DISABLED) {}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal fun interface JavascriptBackHandler {
|
||||||
|
@JavascriptInterface
|
||||||
|
fun onBackPressed()
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,19 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.call.impl.ui
|
|
||||||
|
|
||||||
import io.element.android.features.call.api.CallType
|
|
||||||
import io.element.android.libraries.matrix.api.core.SessionId
|
|
||||||
|
|
||||||
fun CallType.getSessionId(): SessionId? {
|
|
||||||
return when (this) {
|
|
||||||
is CallType.ExternalUrl -> null
|
|
||||||
is CallType.RoomCall -> sessionId
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -35,16 +35,14 @@ import androidx.core.util.Consumer
|
||||||
import androidx.lifecycle.Lifecycle
|
import androidx.lifecycle.Lifecycle
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.api.CallType.ExternalUrl
|
|
||||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||||
import io.element.android.features.call.impl.di.CallBindings
|
import io.element.android.features.call.impl.di.CallBindings
|
||||||
import io.element.android.features.call.impl.pip.PictureInPictureEvents
|
import io.element.android.features.call.impl.pip.PictureInPictureEvent
|
||||||
import io.element.android.features.call.impl.pip.PictureInPicturePresenter
|
import io.element.android.features.call.impl.pip.PictureInPicturePresenter
|
||||||
import io.element.android.features.call.impl.pip.PictureInPictureState
|
import io.element.android.features.call.impl.pip.PictureInPictureState
|
||||||
import io.element.android.features.call.impl.pip.PipView
|
import io.element.android.features.call.impl.pip.PipView
|
||||||
import io.element.android.features.call.impl.services.CallForegroundService
|
import io.element.android.features.call.impl.services.CallForegroundService
|
||||||
import io.element.android.features.call.impl.utils.CallIntentDataParser
|
|
||||||
import io.element.android.features.enterprise.api.EnterpriseService
|
import io.element.android.features.enterprise.api.EnterpriseService
|
||||||
import io.element.android.libraries.androidutils.browser.ConsoleMessageLogger
|
import io.element.android.libraries.androidutils.browser.ConsoleMessageLogger
|
||||||
import io.element.android.libraries.architecture.Presenter
|
import io.element.android.libraries.architecture.Presenter
|
||||||
|
|
@ -64,7 +62,6 @@ class ElementCallActivity :
|
||||||
AppCompatActivity(),
|
AppCompatActivity(),
|
||||||
CallScreenNavigator,
|
CallScreenNavigator,
|
||||||
PipView {
|
PipView {
|
||||||
@Inject lateinit var callIntentDataParser: CallIntentDataParser
|
|
||||||
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
|
@Inject lateinit var presenterFactory: CallScreenPresenter.Factory
|
||||||
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
@Inject lateinit var appPreferencesStore: AppPreferencesStore
|
||||||
@Inject lateinit var featureFlagService: FeatureFlagService
|
@Inject lateinit var featureFlagService: FeatureFlagService
|
||||||
|
|
@ -80,9 +77,9 @@ class ElementCallActivity :
|
||||||
|
|
||||||
private val requestPermissionsLauncher = registerPermissionResultLauncher()
|
private val requestPermissionsLauncher = registerPermissionResultLauncher()
|
||||||
|
|
||||||
private val webViewTarget = mutableStateOf<CallType?>(null)
|
private val webViewTarget = mutableStateOf<CallData?>(null)
|
||||||
|
|
||||||
private var eventSink: ((CallScreenEvents) -> Unit)? = null
|
private var eventSink: ((CallScreenEvent) -> Unit)? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
|
|
@ -98,7 +95,7 @@ class ElementCallActivity :
|
||||||
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
window.addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED)
|
||||||
}
|
}
|
||||||
|
|
||||||
setCallType(intent)
|
setCallData(intent)
|
||||||
// If presenter is not created at this point, it means we have no call to display, the Activity is finishing, so return early
|
// If presenter is not created at this point, it means we have no call to display, the Activity is finishing, so return early
|
||||||
if (!::presenter.isInitialized) {
|
if (!::presenter.isInitialized) {
|
||||||
return
|
return
|
||||||
|
|
@ -111,8 +108,8 @@ class ElementCallActivity :
|
||||||
setContent {
|
setContent {
|
||||||
val pipState = pictureInPicturePresenter.present()
|
val pipState = pictureInPicturePresenter.present()
|
||||||
ListenToAndroidEvents(pipState)
|
ListenToAndroidEvents(pipState)
|
||||||
val colors by remember(webViewTarget.value?.getSessionId()) {
|
val colors by remember(webViewTarget.value?.sessionId) {
|
||||||
enterpriseService.semanticColorsFlow(sessionId = webViewTarget.value?.getSessionId())
|
enterpriseService.semanticColorsFlow(sessionId = webViewTarget.value?.sessionId)
|
||||||
}.collectAsState(SemanticColorsLightDark.default)
|
}.collectAsState(SemanticColorsLightDark.default)
|
||||||
ElementThemeApp(
|
ElementThemeApp(
|
||||||
appPreferencesStore = appPreferencesStore,
|
appPreferencesStore = appPreferencesStore,
|
||||||
|
|
@ -123,9 +120,8 @@ class ElementCallActivity :
|
||||||
) {
|
) {
|
||||||
val state = presenter.present()
|
val state = presenter.present()
|
||||||
eventSink = state.eventSink
|
eventSink = state.eventSink
|
||||||
LaunchedEffect(state.isCallActive, state.isInWidgetMode) {
|
LaunchedEffect(state.isCallActive) {
|
||||||
// Note when not in WidgetMode, isCallActive will never be true, so consider the call is active
|
if (state.isCallActive) {
|
||||||
if (state.isCallActive || !state.isInWidgetMode) {
|
|
||||||
setCallIsActive()
|
setCallIsActive()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -163,7 +159,7 @@ class ElementCallActivity :
|
||||||
if (requestPermissionCallback != null) {
|
if (requestPermissionCallback != null) {
|
||||||
Timber.tag(loggerTag.value).w("Ignoring onUserLeaveHint event because user is asked to grant permissions")
|
Timber.tag(loggerTag.value).w("Ignoring onUserLeaveHint event because user is asked to grant permissions")
|
||||||
} else {
|
} else {
|
||||||
pipEventSink(PictureInPictureEvents.EnterPictureInPicture)
|
pipEventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addOnUserLeaveHintListener(listener)
|
addOnUserLeaveHintListener(listener)
|
||||||
|
|
@ -173,10 +169,10 @@ class ElementCallActivity :
|
||||||
}
|
}
|
||||||
DisposableEffect(Unit) {
|
DisposableEffect(Unit) {
|
||||||
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
|
val onPictureInPictureModeChangedListener = Consumer { _: PictureInPictureModeChangedInfo ->
|
||||||
pipEventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(isInPictureInPictureMode))
|
pipEventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(isInPictureInPictureMode))
|
||||||
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
if (!isInPictureInPictureMode && !lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) {
|
||||||
Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call")
|
Timber.tag(loggerTag.value).d("Exiting PiP mode: Hangup the call")
|
||||||
eventSink?.invoke(CallScreenEvents.Hangup)
|
eventSink?.invoke(CallScreenEvent.Hangup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
addOnPictureInPictureModeChangedListener(onPictureInPictureModeChangedListener)
|
addOnPictureInPictureModeChangedListener(onPictureInPictureModeChangedListener)
|
||||||
|
|
@ -188,7 +184,7 @@ class ElementCallActivity :
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
override fun onNewIntent(intent: Intent) {
|
||||||
super.onNewIntent(intent)
|
super.onNewIntent(intent)
|
||||||
setCallType(intent)
|
setCallData(intent)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
|
|
@ -207,25 +203,24 @@ class ElementCallActivity :
|
||||||
finish()
|
finish()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setCallType(intent: Intent?) {
|
private fun setCallData(intent: Intent?) {
|
||||||
val callType = intent?.let {
|
val callData = intent?.let {
|
||||||
IntentCompat.getParcelableExtra(intent, DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, CallType::class.java)
|
IntentCompat.getParcelableExtra(intent, DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, CallData::class.java)
|
||||||
?: intent.dataString?.let(::parseUrl)?.let(::ExternalUrl)
|
|
||||||
}
|
}
|
||||||
val currentCallType = webViewTarget.value
|
val currentCallData = webViewTarget.value
|
||||||
if (currentCallType == null) {
|
if (currentCallData == null) {
|
||||||
if (callType == null) {
|
if (callData == null) {
|
||||||
Timber.tag(loggerTag.value).d("Re-opened the activity but we have no url to load or a cached one, finish the activity")
|
Timber.tag(loggerTag.value).d("Re-opened the activity but we have no url to load or a cached one, finish the activity")
|
||||||
finish()
|
finish()
|
||||||
} else {
|
} else {
|
||||||
Timber.tag(loggerTag.value).d("Set the call type and create the presenter")
|
Timber.tag(loggerTag.value).d("Set the call type and create the presenter")
|
||||||
webViewTarget.value = callType
|
webViewTarget.value = callData
|
||||||
presenter = presenterFactory.create(callType, this)
|
presenter = presenterFactory.create(callData, this)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (callType == null) {
|
if (callData == null) {
|
||||||
Timber.tag(loggerTag.value).d("Coming back from notification, do nothing")
|
Timber.tag(loggerTag.value).d("Coming back from notification, do nothing")
|
||||||
} else if (callType != currentCallType) {
|
} else if (callData != currentCallData) {
|
||||||
Timber.tag(loggerTag.value).d("User starts another call, restart the Activity")
|
Timber.tag(loggerTag.value).d("User starts another call, restart the Activity")
|
||||||
setIntent(intent)
|
setIntent(intent)
|
||||||
recreate()
|
recreate()
|
||||||
|
|
@ -236,8 +231,6 @@ class ElementCallActivity :
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun parseUrl(url: String?): String? = callIntentDataParser.parse(url)
|
|
||||||
|
|
||||||
private fun registerPermissionResultLauncher(): ActivityResultLauncher<Array<String>> {
|
private fun registerPermissionResultLauncher(): ActivityResultLauncher<Array<String>> {
|
||||||
return registerForActivityResult(
|
return registerForActivityResult(
|
||||||
ActivityResultContracts.RequestMultiplePermissions()
|
ActivityResultContracts.RequestMultiplePermissions()
|
||||||
|
|
@ -287,7 +280,7 @@ class ElementCallActivity :
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun hangUp() {
|
override fun hangUp() {
|
||||||
eventSink?.invoke(CallScreenEvents.Hangup)
|
eventSink?.invoke(CallScreenEvent.Hangup)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ import androidx.core.content.IntentCompat
|
||||||
import androidx.lifecycle.lifecycleScope
|
import androidx.lifecycle.lifecycleScope
|
||||||
import dev.zacsweers.metro.Inject
|
import dev.zacsweers.metro.Inject
|
||||||
import io.element.android.compound.colors.SemanticColorsLightDark
|
import io.element.android.compound.colors.SemanticColorsLightDark
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.api.ElementCallEntryPoint
|
import io.element.android.features.call.api.ElementCallEntryPoint
|
||||||
import io.element.android.features.call.impl.di.CallBindings
|
import io.element.android.features.call.impl.di.CallBindings
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
|
|
@ -118,10 +118,10 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||||
|
|
||||||
private fun onAnswer(notificationData: CallNotificationData) {
|
private fun onAnswer(notificationData: CallNotificationData) {
|
||||||
elementCallEntryPoint.startCall(
|
elementCallEntryPoint.startCall(
|
||||||
CallType.RoomCall(
|
CallData(
|
||||||
notificationData.sessionId,
|
sessionId = notificationData.sessionId,
|
||||||
notificationData.roomId,
|
roomId = notificationData.roomId,
|
||||||
isAudioCall = notificationData.audioOnly
|
isAudioCall = notificationData.audioOnly,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -129,7 +129,7 @@ class IncomingCallActivity : AppCompatActivity() {
|
||||||
private fun onCancel() {
|
private fun onCancel() {
|
||||||
val activeCall = activeCallManager.activeCall.value ?: return
|
val activeCall = activeCallManager.activeCall.value ?: return
|
||||||
appCoroutineScope.launch {
|
appCoroutineScope.launch {
|
||||||
activeCallManager.hangUpCall(callType = activeCall.callType)
|
activeCallManager.hangUpCall(callData = activeCall.callData)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -20,7 +20,7 @@ import dev.zacsweers.metro.AppScope
|
||||||
import dev.zacsweers.metro.ContributesBinding
|
import dev.zacsweers.metro.ContributesBinding
|
||||||
import dev.zacsweers.metro.SingleIn
|
import dev.zacsweers.metro.SingleIn
|
||||||
import io.element.android.appconfig.ElementCallConfig
|
import io.element.android.appconfig.ElementCallConfig
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.api.CurrentCall
|
import io.element.android.features.call.api.CurrentCall
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
||||||
|
|
@ -73,20 +73,20 @@ interface ActiveCallManager {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called to hang up the active call. It will hang up the call and remove any existing UI and the active call.
|
* Called to hang up the active call. It will hang up the call and remove any existing UI and the active call.
|
||||||
* @param callType The type of call that the user hangs up, either an external url one or a room one.
|
* @param callData The data about the call.
|
||||||
* @param notificationData The data for the incoming call notification.
|
* @param notificationData The data for the incoming call notification.
|
||||||
*/
|
*/
|
||||||
suspend fun hangUpCall(
|
suspend fun hangUpCall(
|
||||||
callType: CallType,
|
callData: CallData,
|
||||||
notificationData: CallNotificationData? = null,
|
notificationData: CallNotificationData? = null,
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall].
|
* Called after the user joined a call. It will remove any existing UI and set the call state as [CallState.InCall].
|
||||||
*
|
*
|
||||||
* @param callType The type of call that the user joined, either an external url one or a room one.
|
* @param callData The data about the call.
|
||||||
*/
|
*/
|
||||||
suspend fun joinedCall(callType: CallType)
|
suspend fun joinedCall(callData: CallData)
|
||||||
}
|
}
|
||||||
|
|
||||||
@SingleIn(AppScope::class)
|
@SingleIn(AppScope::class)
|
||||||
|
|
@ -143,7 +143,7 @@ class DefaultActiveCallManager(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
activeCall.value = ActiveCall(
|
activeCall.value = ActiveCall(
|
||||||
callType = CallType.RoomCall(
|
callData = CallData(
|
||||||
sessionId = notificationData.sessionId,
|
sessionId = notificationData.sessionId,
|
||||||
roomId = notificationData.roomId,
|
roomId = notificationData.roomId,
|
||||||
isAudioCall = notificationData.audioOnly,
|
isAudioCall = notificationData.audioOnly,
|
||||||
|
|
@ -198,17 +198,17 @@ class DefaultActiveCallManager(
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun hangUpCall(
|
override suspend fun hangUpCall(
|
||||||
callType: CallType,
|
callData: CallData,
|
||||||
notificationData: CallNotificationData?,
|
notificationData: CallNotificationData?,
|
||||||
) = mutex.withLock {
|
) = mutex.withLock {
|
||||||
Timber.tag(tag).d("Hang up call: $callType")
|
Timber.tag(tag).d("Hang up call: $callData")
|
||||||
cancelIncomingCallNotification()
|
cancelIncomingCallNotification()
|
||||||
val currentActiveCall = activeCall.value ?: run {
|
val currentActiveCall = activeCall.value ?: run {
|
||||||
// activeCall.value can be null if the application has been killed while the call was ringing
|
// activeCall.value can be null if the application has been killed while the call was ringing
|
||||||
// Build a currentActiveCall with the provided parameters.
|
// Build a currentActiveCall with the provided parameters.
|
||||||
notificationData?.let {
|
notificationData?.let {
|
||||||
ActiveCall(
|
ActiveCall(
|
||||||
callType = callType,
|
callData = callData,
|
||||||
callState = CallState.Ringing(
|
callState = CallState.Ringing(
|
||||||
notificationData = notificationData,
|
notificationData = notificationData,
|
||||||
)
|
)
|
||||||
|
|
@ -219,8 +219,8 @@ class DefaultActiveCallManager(
|
||||||
return@withLock
|
return@withLock
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentActiveCall.callType != callType) {
|
if (currentActiveCall.callData != callData) {
|
||||||
Timber.tag(tag).w("Call type $callType does not match the active call type, ignoring")
|
Timber.tag(tag).w("Call type $callData does not match the active call type, ignoring")
|
||||||
return@withLock
|
return@withLock
|
||||||
}
|
}
|
||||||
if (currentActiveCall.callState is CallState.Ringing) {
|
if (currentActiveCall.callState is CallState.Ringing) {
|
||||||
|
|
@ -244,8 +244,8 @@ class DefaultActiveCallManager(
|
||||||
activeCall.value = null
|
activeCall.value = null
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun joinedCall(callType: CallType) = mutex.withLock {
|
override suspend fun joinedCall(callData: CallData) = mutex.withLock {
|
||||||
Timber.tag(tag).d("Joined call: $callType")
|
Timber.tag(tag).d("Joined call: $callData")
|
||||||
cancelIncomingCallNotification()
|
cancelIncomingCallNotification()
|
||||||
if (activeWakeLock?.isHeld == true) {
|
if (activeWakeLock?.isHeld == true) {
|
||||||
Timber.tag(tag).d("Releasing partial wakelock after joining call")
|
Timber.tag(tag).d("Releasing partial wakelock after joining call")
|
||||||
|
|
@ -254,7 +254,7 @@ class DefaultActiveCallManager(
|
||||||
timedOutCallJob?.cancel()
|
timedOutCallJob?.cancel()
|
||||||
|
|
||||||
activeCall.value = ActiveCall(
|
activeCall.value = ActiveCall(
|
||||||
callType = callType,
|
callData = callData,
|
||||||
callState = CallState.InCall,
|
callState = CallState.InCall,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -307,15 +307,15 @@ class DefaultActiveCallManager(
|
||||||
private fun observeRingingCall() {
|
private fun observeRingingCall() {
|
||||||
activeCall
|
activeCall
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
|
.filter { it.callState is CallState.Ringing }
|
||||||
.flatMapLatest { activeCall ->
|
.flatMapLatest { activeCall ->
|
||||||
val callType = activeCall.callType as CallType.RoomCall
|
val callData = activeCall.callData
|
||||||
val ringingInfo = activeCall.callState as CallState.Ringing
|
val ringingInfo = activeCall.callState as CallState.Ringing
|
||||||
val client = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull() ?: run {
|
val client = matrixClientProvider.getOrRestore(callData.sessionId).getOrNull() ?: run {
|
||||||
Timber.tag(tag).d("Couldn't find session for incoming call: $activeCall")
|
Timber.tag(tag).d("Couldn't find session for incoming call: $activeCall")
|
||||||
return@flatMapLatest flowOf()
|
return@flatMapLatest flowOf()
|
||||||
}
|
}
|
||||||
val room = client.getRoom(callType.roomId) ?: run {
|
val room = client.getRoom(callData.roomId) ?: run {
|
||||||
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
||||||
return@flatMapLatest flowOf()
|
return@flatMapLatest flowOf()
|
||||||
}
|
}
|
||||||
|
|
@ -346,17 +346,17 @@ class DefaultActiveCallManager(
|
||||||
// has joined the call from another session.
|
// has joined the call from another session.
|
||||||
activeCall
|
activeCall
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
.filter { it.callState is CallState.Ringing && it.callType is CallType.RoomCall }
|
.filter { it.callState is CallState.Ringing }
|
||||||
.flatMapLatest { activeCall ->
|
.flatMapLatest { activeCall ->
|
||||||
val callType = activeCall.callType as CallType.RoomCall
|
val callData = activeCall.callData
|
||||||
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
|
// Get a flow of updated `hasRoomCall` and `activeRoomCallParticipants` values for the room
|
||||||
val room = matrixClientProvider.getOrRestore(callType.sessionId).getOrNull()?.getRoom(callType.roomId) ?: run {
|
val room = matrixClientProvider.getOrRestore(callData.sessionId).getOrNull()?.getRoom(callData.roomId) ?: run {
|
||||||
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
Timber.tag(tag).d("Couldn't find room for incoming call: $activeCall")
|
||||||
return@flatMapLatest flowOf()
|
return@flatMapLatest flowOf()
|
||||||
}
|
}
|
||||||
room.roomInfoFlow.map {
|
room.roomInfoFlow.map {
|
||||||
Timber.tag(tag).d("Has room call status changed for ringing call: ${it.hasRoomCall}")
|
Timber.tag(tag).d("Has room call status changed for ringing call: ${it.hasRoomCall}")
|
||||||
it.hasRoomCall to (callType.sessionId in it.activeRoomCallParticipants)
|
it.hasRoomCall to (callData.sessionId in it.activeRoomCallParticipants)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// We only want to check if the room active call status changes
|
// We only want to check if the room active call status changes
|
||||||
|
|
@ -388,10 +388,7 @@ class DefaultActiveCallManager(
|
||||||
// Nothing to do
|
// Nothing to do
|
||||||
}
|
}
|
||||||
is CallState.InCall -> {
|
is CallState.InCall -> {
|
||||||
when (val callType = value.callType) {
|
defaultCurrentCallService.onCallStarted(CurrentCall.RoomCall(value.callData.roomId))
|
||||||
is CallType.ExternalUrl -> defaultCurrentCallService.onCallStarted(CurrentCall.ExternalUrl(callType.url))
|
|
||||||
is CallType.RoomCall -> defaultCurrentCallService.onCallStarted(CurrentCall.RoomCall(callType.roomId))
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -404,7 +401,7 @@ class DefaultActiveCallManager(
|
||||||
* Represents an active call.
|
* Represents an active call.
|
||||||
*/
|
*/
|
||||||
data class ActiveCall(
|
data class ActiveCall(
|
||||||
val callType: CallType,
|
val callData: CallData,
|
||||||
val callState: CallState,
|
val callState: CallState,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2023-2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.call.impl.utils
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import dev.zacsweers.metro.Inject
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
class CallIntentDataParser {
|
|
||||||
private val validHttpSchemes = sequenceOf("https")
|
|
||||||
private val knownHosts = sequenceOf(
|
|
||||||
"call.element.io",
|
|
||||||
)
|
|
||||||
|
|
||||||
fun parse(data: String?): String? {
|
|
||||||
val parsedUrl = data?.toUri() ?: return null
|
|
||||||
val scheme = parsedUrl.scheme
|
|
||||||
return when {
|
|
||||||
scheme in validHttpSchemes -> parsedUrl
|
|
||||||
scheme == "element" && parsedUrl.host == "call" -> {
|
|
||||||
parsedUrl.getUrlParameter()
|
|
||||||
}
|
|
||||||
scheme == "io.element.call" && parsedUrl.host == null -> {
|
|
||||||
parsedUrl.getUrlParameter()
|
|
||||||
}
|
|
||||||
// This should never be possible, but we still need to take into account the possibility
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
?.takeIf { it.host in knownHosts }
|
|
||||||
?.withCustomParameters()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun Uri.getUrlParameter(): Uri? {
|
|
||||||
return getQueryParameter("url")
|
|
||||||
?.let { urlParameter ->
|
|
||||||
urlParameter.toUri().takeIf { uri ->
|
|
||||||
uri.scheme in validHttpSchemes && !uri.host.isNullOrBlank()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Ensure the uri has the following parameters and value in the fragment:
|
|
||||||
* - appPrompt=false
|
|
||||||
* - confineToRoom=true
|
|
||||||
* to ensure that the rendering will bo correct on the embedded Webview.
|
|
||||||
*/
|
|
||||||
private fun Uri.withCustomParameters(): String {
|
|
||||||
val builder = buildUpon()
|
|
||||||
// Remove the existing query parameters
|
|
||||||
builder.clearQuery()
|
|
||||||
queryParameterNames.forEach {
|
|
||||||
if (it == APP_PROMPT_PARAMETER || it == CONFINE_TO_ROOM_PARAMETER) return@forEach
|
|
||||||
builder.appendQueryParameter(it, getQueryParameter(it))
|
|
||||||
}
|
|
||||||
// Remove the existing fragment parameters, and build the new fragment
|
|
||||||
val currentFragment = fragment ?: ""
|
|
||||||
// Reset the current fragment
|
|
||||||
builder.fragment("")
|
|
||||||
val queryFragmentPosition = currentFragment.lastIndexOf("?")
|
|
||||||
val newFragment = if (queryFragmentPosition == -1) {
|
|
||||||
// No existing query, build it.
|
|
||||||
"$currentFragment?$APP_PROMPT_PARAMETER=false&$CONFINE_TO_ROOM_PARAMETER=true"
|
|
||||||
} else {
|
|
||||||
buildString {
|
|
||||||
append(currentFragment.substring(0, queryFragmentPosition + 1))
|
|
||||||
val queryFragment = currentFragment.substring(queryFragmentPosition + 1)
|
|
||||||
// Replace the existing parameters
|
|
||||||
val newQueryFragment = queryFragment
|
|
||||||
.replace("$APP_PROMPT_PARAMETER=true", "$APP_PROMPT_PARAMETER=false")
|
|
||||||
.replace("$CONFINE_TO_ROOM_PARAMETER=false", "$CONFINE_TO_ROOM_PARAMETER=true")
|
|
||||||
append(newQueryFragment)
|
|
||||||
// Ensure the parameters are there
|
|
||||||
if (!newQueryFragment.contains("$APP_PROMPT_PARAMETER=false")) {
|
|
||||||
if (newQueryFragment.isNotEmpty()) {
|
|
||||||
append("&")
|
|
||||||
}
|
|
||||||
append("$APP_PROMPT_PARAMETER=false")
|
|
||||||
}
|
|
||||||
if (!newQueryFragment.contains("$CONFINE_TO_ROOM_PARAMETER=true")) {
|
|
||||||
append("&$CONFINE_TO_ROOM_PARAMETER=true")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// We do not want to encode the Fragment part, so append it manually
|
|
||||||
return builder.build().toString() + "#" + newFragment
|
|
||||||
}
|
|
||||||
|
|
||||||
private const val APP_PROMPT_PARAMETER = "appPrompt"
|
|
||||||
private const val CONFINE_TO_ROOM_PARAMETER = "confineToRoom"
|
|
||||||
|
|
@ -12,21 +12,21 @@ import android.app.PendingIntent
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.core.app.PendingIntentCompat
|
import androidx.core.app.PendingIntentCompat
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||||
import io.element.android.features.call.impl.ui.ElementCallActivity
|
import io.element.android.features.call.impl.ui.ElementCallActivity
|
||||||
|
|
||||||
internal object IntentProvider {
|
internal object IntentProvider {
|
||||||
fun createIntent(context: Context, callType: CallType): Intent = Intent(context, ElementCallActivity::class.java).apply {
|
fun createIntent(context: Context, callData: CallData): Intent = Intent(context, ElementCallActivity::class.java).apply {
|
||||||
putExtra(DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, callType)
|
putExtra(DefaultElementCallEntryPoint.EXTRA_CALL_TYPE, callData)
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_NO_USER_ACTION)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getPendingIntent(context: Context, callType: CallType): PendingIntent {
|
fun getPendingIntent(context: Context, callData: CallData): PendingIntent {
|
||||||
return PendingIntentCompat.getActivity(
|
return PendingIntentCompat.getActivity(
|
||||||
context,
|
context,
|
||||||
DefaultElementCallEntryPoint.REQUEST_CODE,
|
DefaultElementCallEntryPoint.REQUEST_CODE,
|
||||||
createIntent(context, callType),
|
createIntent(context, callData),
|
||||||
PendingIntent.FLAG_CANCEL_CURRENT,
|
PendingIntent.FLAG_CANCEL_CURRENT,
|
||||||
false
|
false
|
||||||
)!!
|
)!!
|
||||||
|
|
|
||||||
|
|
@ -140,26 +140,33 @@ class WebViewWidgetMessageInterceptor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a WebMessageListener, which will receive messages from the WebView and reply to them
|
// Always register JavascriptInterface as the baseline message channel.
|
||||||
val webMessageListener = WebViewCompat.WebMessageListener { _, message, _, _, _ ->
|
// This works on all WebView implementations including Huawei.
|
||||||
onMessageReceived(message.data)
|
webView.addJavascriptInterface(object {
|
||||||
}
|
@JavascriptInterface
|
||||||
|
fun postMessage(json: String?) {
|
||||||
|
onMessageReceived(json)
|
||||||
|
}
|
||||||
|
}, LISTENER_NAME)
|
||||||
|
|
||||||
// Use WebMessageListener if supported, otherwise use JavascriptInterface
|
// Additionally register WebMessageListener on WebViews that reliably support it.
|
||||||
if (WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
// Huawei WebView (Chromium < 119) reports WEB_MESSAGE_LISTENER as supported
|
||||||
|
// but silently drops messages, so we only trust it on Chromium 119+.
|
||||||
|
// See: https://github.com/element-hq/element-x-android/issues/6632
|
||||||
|
val webViewVersionName = WebViewCompat.getCurrentWebViewPackage(webView.context)?.versionName.orEmpty()
|
||||||
|
Timber.d("Using WebView version: $webViewVersionName")
|
||||||
|
val webViewVersionCode = webViewVersionName.split(".").firstOrNull()?.toIntOrNull() ?: 0
|
||||||
|
|
||||||
|
if (webViewVersionCode >= 119 &&
|
||||||
|
WebViewFeature.isFeatureSupported(WebViewFeature.WEB_MESSAGE_LISTENER)) {
|
||||||
WebViewCompat.addWebMessageListener(
|
WebViewCompat.addWebMessageListener(
|
||||||
webView,
|
webView,
|
||||||
LISTENER_NAME,
|
LISTENER_NAME,
|
||||||
setOf("*"),
|
setOf("*"),
|
||||||
webMessageListener
|
WebViewCompat.WebMessageListener { _, message, _, _, _ ->
|
||||||
)
|
onMessageReceived(message.data)
|
||||||
} else {
|
|
||||||
webView.addJavascriptInterface(object {
|
|
||||||
@JavascriptInterface
|
|
||||||
fun postMessage(json: String?) {
|
|
||||||
onMessageReceived(json)
|
|
||||||
}
|
}
|
||||||
}, LISTENER_NAME)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ package io.element.android.features.call
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
import io.element.android.features.call.impl.DefaultElementCallEntryPoint
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.ui.ElementCallActivity
|
import io.element.android.features.call.impl.ui.ElementCallActivity
|
||||||
|
|
@ -37,7 +37,7 @@ class DefaultElementCallEntryPointTest {
|
||||||
@Test
|
@Test
|
||||||
fun `startCall - starts ElementCallActivity setup with the needed extras`() = runTest {
|
fun `startCall - starts ElementCallActivity setup with the needed extras`() = runTest {
|
||||||
val entryPoint = createEntryPoint()
|
val entryPoint = createEntryPoint()
|
||||||
entryPoint.startCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, isAudioCall = false))
|
entryPoint.startCall(CallData(A_SESSION_ID, A_ROOM_ID, isAudioCall = false))
|
||||||
|
|
||||||
val expectedIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, ElementCallActivity::class.java)
|
val expectedIntent = Intent(InstrumentationRegistry.getInstrumentation().targetContext, ElementCallActivity::class.java)
|
||||||
val intent = shadowOf(RuntimeEnvironment.getApplication()).nextStartedActivity
|
val intent = shadowOf(RuntimeEnvironment.getApplication()).nextStartedActivity
|
||||||
|
|
@ -53,7 +53,7 @@ class DefaultElementCallEntryPointTest {
|
||||||
val entryPoint = createEntryPoint(activeCallManager = activeCallManager)
|
val entryPoint = createEntryPoint(activeCallManager = activeCallManager)
|
||||||
|
|
||||||
entryPoint.handleIncomingCall(
|
entryPoint.handleIncomingCall(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, isAudioCall = false),
|
callData = CallData(A_SESSION_ID, A_ROOM_ID, isAudioCall = false),
|
||||||
eventId = AN_EVENT_ID,
|
eventId = AN_EVENT_ID,
|
||||||
senderId = A_USER_ID_2,
|
senderId = A_USER_ID_2,
|
||||||
roomName = "roomName",
|
roomName = "roomName",
|
||||||
|
|
|
||||||
|
|
@ -8,11 +8,9 @@
|
||||||
|
|
||||||
package io.element.android.features.call.impl.pip
|
package io.element.android.features.call.impl.pip
|
||||||
|
|
||||||
import app.cash.molecule.RecompositionMode
|
|
||||||
import app.cash.molecule.moleculeFlow
|
|
||||||
import app.cash.turbine.test
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
|
import io.element.android.tests.testutils.test
|
||||||
import kotlinx.coroutines.test.runTest
|
import kotlinx.coroutines.test.runTest
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
|
|
@ -20,9 +18,7 @@ class PictureInPicturePresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `when pip is not supported, the state value supportPip is false`() = runTest {
|
fun `when pip is not supported, the state value supportPip is false`() = runTest {
|
||||||
val presenter = createPictureInPicturePresenter(supportPip = false)
|
val presenter = createPictureInPicturePresenter(supportPip = false)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.supportPip).isFalse()
|
assertThat(initialState.supportPip).isFalse()
|
||||||
}
|
}
|
||||||
|
|
@ -35,9 +31,7 @@ class PictureInPicturePresenterTest {
|
||||||
supportPip = true,
|
supportPip = true,
|
||||||
pipView = FakePipView(setPipParamsResult = { }),
|
pipView = FakePipView(setPipParamsResult = { }),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.supportPip).isTrue()
|
assertThat(initialState.supportPip).isTrue()
|
||||||
}
|
}
|
||||||
|
|
@ -53,18 +47,16 @@ class PictureInPicturePresenterTest {
|
||||||
enterPipModeResult = enterPipModeResult,
|
enterPipModeResult = enterPipModeResult,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isInPictureInPicture).isFalse()
|
assertThat(initialState.isInPictureInPicture).isFalse()
|
||||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||||
enterPipModeResult.assertions().isCalledOnce()
|
enterPipModeResult.assertions().isCalledOnce()
|
||||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
|
initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(true))
|
||||||
val pipState = awaitItem()
|
val pipState = awaitItem()
|
||||||
assertThat(pipState.isInPictureInPicture).isTrue()
|
assertThat(pipState.isInPictureInPicture).isTrue()
|
||||||
// User stops pip
|
// User stops pip
|
||||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
|
initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(false))
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.isInPictureInPicture).isFalse()
|
assertThat(finalState.isInPictureInPicture).isFalse()
|
||||||
}
|
}
|
||||||
|
|
@ -80,12 +72,10 @@ class PictureInPicturePresenterTest {
|
||||||
handUpResult = handUpResult
|
handUpResult = handUpResult
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(PictureInPictureEvents.SetPipController(FakePipController(canEnterPipResult = { false })))
|
initialState.eventSink(PictureInPictureEvent.SetPipController(FakePipController(canEnterPipResult = { false })))
|
||||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||||
handUpResult.assertions().isCalledOnce()
|
handUpResult.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -102,12 +92,10 @@ class PictureInPicturePresenterTest {
|
||||||
enterPipModeResult = enterPipModeResult
|
enterPipModeResult = enterPipModeResult
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(
|
initialState.eventSink(
|
||||||
PictureInPictureEvents.SetPipController(
|
PictureInPictureEvent.SetPipController(
|
||||||
FakePipController(
|
FakePipController(
|
||||||
canEnterPipResult = { true },
|
canEnterPipResult = { true },
|
||||||
enterPipResult = enterPipResult,
|
enterPipResult = enterPipResult,
|
||||||
|
|
@ -115,16 +103,16 @@ class PictureInPicturePresenterTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
initialState.eventSink(PictureInPictureEvents.EnterPictureInPicture)
|
initialState.eventSink(PictureInPictureEvent.EnterPictureInPicture)
|
||||||
enterPipModeResult.assertions().isCalledOnce()
|
enterPipModeResult.assertions().isCalledOnce()
|
||||||
enterPipResult.assertions().isNeverCalled()
|
enterPipResult.assertions().isNeverCalled()
|
||||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(true))
|
initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(true))
|
||||||
val pipState = awaitItem()
|
val pipState = awaitItem()
|
||||||
assertThat(pipState.isInPictureInPicture).isTrue()
|
assertThat(pipState.isInPictureInPicture).isTrue()
|
||||||
enterPipResult.assertions().isCalledOnce()
|
enterPipResult.assertions().isCalledOnce()
|
||||||
// User stops pip
|
// User stops pip
|
||||||
exitPipResult.assertions().isNeverCalled()
|
exitPipResult.assertions().isNeverCalled()
|
||||||
initialState.eventSink(PictureInPictureEvents.OnPictureInPictureModeChanged(false))
|
initialState.eventSink(PictureInPictureEvent.OnPictureInPictureModeChanged(false))
|
||||||
val finalState = awaitItem()
|
val finalState = awaitItem()
|
||||||
assertThat(finalState.isInPictureInPicture).isFalse()
|
assertThat(finalState.isInPictureInPicture).isFalse()
|
||||||
exitPipResult.assertions().isCalledOnce()
|
exitPipResult.assertions().isCalledOnce()
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025 Element Creations Ltd.
|
||||||
|
* Copyright 2025 New Vector Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.ui
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.call.api.CallData
|
||||||
|
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||||
|
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class CallDataTest {
|
||||||
|
@Test
|
||||||
|
fun `RoomCall stringification does not contain the URL`() {
|
||||||
|
assertThat(CallData(A_SESSION_ID, A_ROOM_ID, false).toString())
|
||||||
|
.isEqualTo("CallData(sessionId=$A_SESSION_ID, roomId=$A_ROOM_ID, isAudioCall=false)")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,96 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package io.element.android.features.call.ui
|
||||||
|
|
||||||
|
import com.google.common.truth.Truth.assertThat
|
||||||
|
import io.element.android.features.call.impl.ui.CallScreenBackPressAction
|
||||||
|
import io.element.android.features.call.impl.ui.CallScreenBackPressPolicy
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
class CallScreenBackPressPolicyTest {
|
||||||
|
@Test
|
||||||
|
fun `resolve returns dispatch escape when a web view is available and native button is pressed`() {
|
||||||
|
val result = CallScreenBackPressPolicy.resolve(
|
||||||
|
supportPip = false,
|
||||||
|
hasWebView = true,
|
||||||
|
fromNative = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(CallScreenBackPressAction.DispatchEscapeToWebView)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve dispatch escape when there is a web view and pip is supported on native button press`() {
|
||||||
|
val result = CallScreenBackPressPolicy.resolve(
|
||||||
|
supportPip = true,
|
||||||
|
hasWebView = true,
|
||||||
|
fromNative = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(CallScreenBackPressAction.DispatchEscapeToWebView)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve returns hangup when there is no web view and pip is not supported from native button`() {
|
||||||
|
val result = CallScreenBackPressPolicy.resolve(
|
||||||
|
supportPip = false,
|
||||||
|
hasWebView = false,
|
||||||
|
fromNative = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve returns hangup when there is no web view even though pip is supported from native button`() {
|
||||||
|
val result = CallScreenBackPressPolicy.resolve(
|
||||||
|
supportPip = true,
|
||||||
|
hasWebView = false,
|
||||||
|
fromNative = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `resolve goes to pip if its not from native but from the webview`() {
|
||||||
|
val result = CallScreenBackPressPolicy.resolve(
|
||||||
|
supportPip = true,
|
||||||
|
hasWebView = true,
|
||||||
|
fromNative = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isEqualTo(CallScreenBackPressAction.EnterPictureInPicture)
|
||||||
|
}
|
||||||
|
@Test
|
||||||
|
fun `resolve hangs up if its not from native but from the webview and pip is not supported`() {
|
||||||
|
val result = CallScreenBackPressPolicy.resolve(
|
||||||
|
supportPip = false,
|
||||||
|
hasWebView = true,
|
||||||
|
fromNative = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
assertThat(result).isNull()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
fun `invalid cases (event comes from webview but there is now webview) all result in hangup`() {
|
||||||
|
val withPipSupport = CallScreenBackPressPolicy.resolve(
|
||||||
|
supportPip = true,
|
||||||
|
hasWebView = false,
|
||||||
|
fromNative = false,
|
||||||
|
)
|
||||||
|
assertThat(withPipSupport).isNull()
|
||||||
|
val withOutPipSupport = CallScreenBackPressPolicy.resolve(
|
||||||
|
supportPip = false,
|
||||||
|
hasWebView = false,
|
||||||
|
fromNative = false,
|
||||||
|
)
|
||||||
|
assertThat(withOutPipSupport).isNull()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -13,8 +13,8 @@ import app.cash.molecule.moleculeFlow
|
||||||
import app.cash.turbine.test
|
import app.cash.turbine.test
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.MobileScreen
|
import im.vector.app.features.analytics.plan.MobileScreen
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.impl.ui.CallScreenEvents
|
import io.element.android.features.call.impl.ui.CallScreenEvent
|
||||||
import io.element.android.features.call.impl.ui.CallScreenNavigator
|
import io.element.android.features.call.impl.ui.CallScreenNavigator
|
||||||
import io.element.android.features.call.impl.ui.CallScreenPresenter
|
import io.element.android.features.call.impl.ui.CallScreenPresenter
|
||||||
import io.element.android.features.call.impl.utils.WidgetMessageSerializer
|
import io.element.android.features.call.impl.utils.WidgetMessageSerializer
|
||||||
|
|
@ -39,6 +39,7 @@ import io.element.android.services.toolbox.api.systemclock.SystemClock
|
||||||
import io.element.android.tests.testutils.WarmUpRule
|
import io.element.android.tests.testutils.WarmUpRule
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.tests.testutils.lambda.value
|
import io.element.android.tests.testutils.lambda.value
|
||||||
|
import io.element.android.tests.testutils.test
|
||||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||||
import kotlinx.coroutines.cancelAndJoin
|
import kotlinx.coroutines.cancelAndJoin
|
||||||
|
|
@ -59,46 +60,19 @@ class CallScreenPresenterTest {
|
||||||
val warmUpRule = WarmUpRule()
|
val warmUpRule = WarmUpRule()
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `present - with CallType ExternalUrl just loads the URL and sets the call as active`() = runTest {
|
fun `present - with CallData sets call as active, loads URL and runs WidgetDriver`() = runTest {
|
||||||
val analyticsLambda = lambdaRecorder<MobileScreen.ScreenName, Unit> {}
|
|
||||||
val joinedCallLambda = lambdaRecorder<CallType, Unit> {}
|
|
||||||
val presenter = createCallScreenPresenter(
|
|
||||||
callType = CallType.ExternalUrl("https://call.element.io"),
|
|
||||||
screenTracker = FakeScreenTracker(analyticsLambda),
|
|
||||||
activeCallManager = FakeActiveCallManager(joinedCallResult = joinedCallLambda),
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
// Wait until the URL is loaded
|
|
||||||
advanceTimeBy(1.seconds)
|
|
||||||
skipItems(2)
|
|
||||||
val initialState = awaitItem()
|
|
||||||
assertThat(initialState.urlState).isEqualTo(AsyncData.Success("https://call.element.io"))
|
|
||||||
assertThat(initialState.webViewError).isNull()
|
|
||||||
assertThat(initialState.isInWidgetMode).isFalse()
|
|
||||||
assertThat(initialState.isCallActive).isFalse()
|
|
||||||
analyticsLambda.assertions().isNeverCalled()
|
|
||||||
joinedCallLambda.assertions().isCalledOnce()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - with CallType RoomCall sets call as active, loads URL and runs WidgetDriver`() = runTest {
|
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val widgetProvider = FakeCallWidgetProvider(widgetDriver)
|
val widgetProvider = FakeCallWidgetProvider(widgetDriver)
|
||||||
val analyticsLambda = lambdaRecorder<MobileScreen.ScreenName, Unit> {}
|
val analyticsLambda = lambdaRecorder<MobileScreen.ScreenName, Unit> {}
|
||||||
val joinedCallLambda = lambdaRecorder<CallType, Unit> {}
|
val joinedCallLambda = lambdaRecorder<CallData, Unit> {}
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
|
callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
widgetProvider = widgetProvider,
|
widgetProvider = widgetProvider,
|
||||||
screenTracker = FakeScreenTracker(analyticsLambda),
|
screenTracker = FakeScreenTracker(analyticsLambda),
|
||||||
activeCallManager = FakeActiveCallManager(joinedCallResult = joinedCallLambda),
|
activeCallManager = FakeActiveCallManager(joinedCallResult = joinedCallLambda),
|
||||||
)
|
)
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
// Wait until the URL is loaded
|
// Wait until the URL is loaded
|
||||||
advanceTimeBy(1.seconds)
|
advanceTimeBy(1.seconds)
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
@ -107,7 +81,6 @@ class CallScreenPresenterTest {
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.urlState).isInstanceOf(AsyncData.Loading::class.java)
|
assertThat(initialState.urlState).isInstanceOf(AsyncData.Loading::class.java)
|
||||||
assertThat(initialState.isCallActive).isFalse()
|
assertThat(initialState.isCallActive).isFalse()
|
||||||
assertThat(initialState.isInWidgetMode).isTrue()
|
|
||||||
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
assertThat(widgetProvider.getWidgetCalled).isTrue()
|
||||||
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
assertThat(widgetDriver.runCalledCount).isEqualTo(1)
|
||||||
analyticsLambda.assertions().isCalledOnce().with(value(MobileScreen.ScreenName.RoomCall))
|
analyticsLambda.assertions().isCalledOnce().with(value(MobileScreen.ScreenName.RoomCall))
|
||||||
|
|
@ -123,19 +96,17 @@ class CallScreenPresenterTest {
|
||||||
fun `present - set message interceptor, send and receive messages`() = runTest {
|
fun `present - set message interceptor, send and receive messages`() = runTest {
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
|
callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
screenTracker = FakeScreenTracker {},
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
// Give it time to load the URL and WidgetDriver
|
// Give it time to load the URL and WidgetDriver
|
||||||
advanceTimeBy(1.seconds)
|
advanceTimeBy(1.seconds)
|
||||||
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
|
||||||
|
|
||||||
// And incoming message from the Widget Driver is passed to the WebView
|
// And incoming message from the Widget Driver is passed to the WebView
|
||||||
widgetDriver.givenIncomingMessage("A message")
|
widgetDriver.givenIncomingMessage("A message")
|
||||||
|
|
@ -154,24 +125,22 @@ class CallScreenPresenterTest {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
|
callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
screenTracker = FakeScreenTracker {},
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
||||||
// Give it time to load the URL and WidgetDriver
|
// Give it time to load the URL and WidgetDriver
|
||||||
advanceTimeBy(1.seconds)
|
advanceTimeBy(1.seconds)
|
||||||
|
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
|
||||||
|
|
||||||
initialState.eventSink(CallScreenEvents.Hangup)
|
initialState.eventSink(CallScreenEvent.Hangup)
|
||||||
|
|
||||||
// Let background coroutines run and the widget drive be received
|
// Let background coroutines run and the widget drive be received
|
||||||
runCurrent()
|
runCurrent()
|
||||||
|
|
@ -188,22 +157,20 @@ class CallScreenPresenterTest {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
|
callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
screenTracker = FakeScreenTracker {},
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
|
|
||||||
// Give it time to load the URL and WidgetDriver
|
// Give it time to load the URL and WidgetDriver
|
||||||
advanceTimeBy(1.seconds)
|
advanceTimeBy(1.seconds)
|
||||||
|
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
|
||||||
|
|
||||||
messageInterceptor.givenInterceptedMessage("""{"action":"io.element.close","api":"fromWidget","widgetId":"1","requestId":"1"}""")
|
messageInterceptor.givenInterceptedMessage("""{"action":"io.element.close","api":"fromWidget","widgetId":"1","requestId":"1"}""")
|
||||||
|
|
||||||
|
|
@ -223,22 +190,20 @@ class CallScreenPresenterTest {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
|
callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
screenTracker = FakeScreenTracker {},
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
// Give it time to load the URL and WidgetDriver
|
// Give it time to load the URL and WidgetDriver
|
||||||
advanceTimeBy(1.seconds)
|
advanceTimeBy(1.seconds)
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isCallActive).isFalse()
|
assertThat(initialState.isCallActive).isFalse()
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
|
||||||
messageInterceptor.givenInterceptedMessage(
|
messageInterceptor.givenInterceptedMessage(
|
||||||
"""
|
"""
|
||||||
{
|
{
|
||||||
|
|
@ -260,22 +225,20 @@ class CallScreenPresenterTest {
|
||||||
val navigator = FakeCallScreenNavigator()
|
val navigator = FakeCallScreenNavigator()
|
||||||
val widgetDriver = FakeMatrixWidgetDriver()
|
val widgetDriver = FakeMatrixWidgetDriver()
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
|
callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
screenTracker = FakeScreenTracker {},
|
screenTracker = FakeScreenTracker {},
|
||||||
)
|
)
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
val messageInterceptor = FakeWidgetMessageInterceptor()
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
presenter.test {
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
// Give it time to load the URL and WidgetDriver
|
// Give it time to load the URL and WidgetDriver
|
||||||
advanceTimeBy(1.seconds)
|
advanceTimeBy(1.seconds)
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
val initialState = awaitItem()
|
val initialState = awaitItem()
|
||||||
assertThat(initialState.isCallActive).isFalse()
|
assertThat(initialState.isCallActive).isFalse()
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
initialState.eventSink(CallScreenEvent.SetupMessageChannels(messageInterceptor))
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
|
|
||||||
// Wait for the timeout to trigger
|
// Wait for the timeout to trigger
|
||||||
|
|
@ -300,7 +263,7 @@ class CallScreenPresenterTest {
|
||||||
val matrixClient = FakeMatrixClient(syncService = syncService)
|
val matrixClient = FakeMatrixClient(syncService = syncService)
|
||||||
val appForegroundStateService = FakeAppForegroundStateService()
|
val appForegroundStateService = FakeAppForegroundStateService()
|
||||||
val presenter = createCallScreenPresenter(
|
val presenter = createCallScreenPresenter(
|
||||||
callType = CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false),
|
callData = CallData(A_SESSION_ID, A_ROOM_ID, false),
|
||||||
widgetDriver = widgetDriver,
|
widgetDriver = widgetDriver,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
dispatchers = testCoroutineDispatchers(useUnconfinedTestDispatcher = true),
|
||||||
|
|
@ -338,53 +301,8 @@ class CallScreenPresenterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - error from WebView are updating the state`() = runTest {
|
|
||||||
val presenter = createCallScreenPresenter(
|
|
||||||
callType = CallType.ExternalUrl("https://call.element.io"),
|
|
||||||
activeCallManager = FakeActiveCallManager(),
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
// Wait until the URL is loaded
|
|
||||||
advanceTimeBy(1.seconds)
|
|
||||||
skipItems(2)
|
|
||||||
val initialState = awaitItem()
|
|
||||||
initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
|
|
||||||
val finalState = awaitItem()
|
|
||||||
assertThat(finalState.webViewError).isEqualTo("A Webview error")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `present - error from WebView are ignored if Element Call is loaded`() = runTest {
|
|
||||||
val presenter = createCallScreenPresenter(
|
|
||||||
callType = CallType.ExternalUrl("https://call.element.io"),
|
|
||||||
activeCallManager = FakeActiveCallManager(),
|
|
||||||
)
|
|
||||||
moleculeFlow(RecompositionMode.Immediate) {
|
|
||||||
presenter.present()
|
|
||||||
}.test {
|
|
||||||
// Wait until the URL is loaded
|
|
||||||
skipItems(1)
|
|
||||||
val initialState = awaitItem()
|
|
||||||
|
|
||||||
val messageInterceptor = FakeWidgetMessageInterceptor()
|
|
||||||
initialState.eventSink(CallScreenEvents.SetupMessageChannels(messageInterceptor))
|
|
||||||
// Emit a message
|
|
||||||
messageInterceptor.givenInterceptedMessage("A message")
|
|
||||||
// WebView emits an error, but it will be ignored
|
|
||||||
initialState.eventSink(CallScreenEvents.OnWebViewError("A Webview error"))
|
|
||||||
val finalState = awaitItem()
|
|
||||||
assertThat(finalState.webViewError).isNull()
|
|
||||||
|
|
||||||
cancelAndIgnoreRemainingEvents()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun TestScope.createCallScreenPresenter(
|
private fun TestScope.createCallScreenPresenter(
|
||||||
callType: CallType,
|
callData: CallData,
|
||||||
navigator: CallScreenNavigator = FakeCallScreenNavigator(),
|
navigator: CallScreenNavigator = FakeCallScreenNavigator(),
|
||||||
widgetDriver: FakeMatrixWidgetDriver = FakeMatrixWidgetDriver(),
|
widgetDriver: FakeMatrixWidgetDriver = FakeMatrixWidgetDriver(),
|
||||||
widgetProvider: FakeCallWidgetProvider = FakeCallWidgetProvider(widgetDriver),
|
widgetProvider: FakeCallWidgetProvider = FakeCallWidgetProvider(widgetDriver),
|
||||||
|
|
@ -401,7 +319,7 @@ class CallScreenPresenterTest {
|
||||||
}
|
}
|
||||||
val clock = SystemClock { 0 }
|
val clock = SystemClock { 0 }
|
||||||
return CallScreenPresenter(
|
return CallScreenPresenter(
|
||||||
callType = callType,
|
callData = callData,
|
||||||
navigator = navigator,
|
navigator = navigator,
|
||||||
callWidgetProvider = widgetProvider,
|
callWidgetProvider = widgetProvider,
|
||||||
userAgentProvider = userAgentProvider,
|
userAgentProvider = userAgentProvider,
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,148 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2026 Element Creations Ltd.
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
||||||
|
* Please see LICENSE files in the repository root for full details.
|
||||||
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
|
package io.element.android.features.call.ui
|
||||||
|
|
||||||
|
import android.view.KeyEvent
|
||||||
|
import android.webkit.WebView
|
||||||
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
import io.element.android.features.call.impl.pip.PictureInPictureEvent
|
||||||
|
import io.element.android.features.call.impl.pip.aPictureInPictureState
|
||||||
|
import io.element.android.features.call.impl.ui.CallScreenEvent
|
||||||
|
import io.element.android.features.call.impl.ui.CallScreenView
|
||||||
|
import io.element.android.features.call.impl.ui.JavascriptBackHandler
|
||||||
|
import io.element.android.features.call.impl.ui.aCallScreenState
|
||||||
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
|
import org.junit.Assert.assertEquals
|
||||||
|
import org.junit.Test
|
||||||
|
import org.junit.runner.RunWith
|
||||||
|
import org.robolectric.annotation.Config
|
||||||
|
import org.robolectric.annotation.Implementation
|
||||||
|
import org.robolectric.annotation.Implements
|
||||||
|
import org.robolectric.annotation.Resetter
|
||||||
|
import org.robolectric.shadows.ShadowWebView
|
||||||
|
|
||||||
|
@RunWith(AndroidJUnit4::class)
|
||||||
|
class CallScreenViewTest {
|
||||||
|
@Test
|
||||||
|
fun `pressing back key triggers hangup when no web view is available and pip is unsupported`() = runAndroidComposeUiTest {
|
||||||
|
val callEvents = EventsRecorder<CallScreenEvent>()
|
||||||
|
|
||||||
|
setCallScreenView(
|
||||||
|
state = aCallScreenState(eventSink = callEvents),
|
||||||
|
useInspectionMode = true,
|
||||||
|
)
|
||||||
|
|
||||||
|
pressBackKey()
|
||||||
|
|
||||||
|
callEvents.assertEmpty()
|
||||||
|
}
|
||||||
|
|
||||||
|
@Config(shadows = [RecordingShadowWebView::class])
|
||||||
|
@Test
|
||||||
|
fun `pressing back key dispatches escape key events to web view when pip is unsupported`() = runAndroidComposeUiTest {
|
||||||
|
setCallScreenView(
|
||||||
|
state = aCallScreenState(),
|
||||||
|
useInspectionMode = false,
|
||||||
|
)
|
||||||
|
|
||||||
|
pressBackKey()
|
||||||
|
|
||||||
|
val dispatchedEvents = RecordingShadowWebView.dispatchedEvents
|
||||||
|
assertEquals(2, dispatchedEvents.size)
|
||||||
|
assertEquals(KeyEvent.ACTION_DOWN, dispatchedEvents[0].action)
|
||||||
|
assertEquals(KeyEvent.KEYCODE_ESCAPE, dispatchedEvents[0].keyCode)
|
||||||
|
assertEquals(KeyEvent.ACTION_UP, dispatchedEvents[1].action)
|
||||||
|
assertEquals(KeyEvent.KEYCODE_ESCAPE, dispatchedEvents[1].keyCode)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Config(shadows = [RecordingShadowWebView::class])
|
||||||
|
@Test
|
||||||
|
fun `web view javascript back handler emits pip event when pip is supported`() = runAndroidComposeUiTest {
|
||||||
|
val pipEvents = EventsRecorder<PictureInPictureEvent>()
|
||||||
|
|
||||||
|
setCallScreenView(
|
||||||
|
state = aCallScreenState(),
|
||||||
|
useInspectionMode = false,
|
||||||
|
pipState = aPictureInPictureState(
|
||||||
|
supportPip = true,
|
||||||
|
eventSink = pipEvents,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
|
||||||
|
runOnIdle {
|
||||||
|
RecordingShadowWebView.invokeJavascriptBackHandler()
|
||||||
|
}
|
||||||
|
|
||||||
|
pipEvents.assertSize(2)
|
||||||
|
pipEvents.assertTrue(0) { it is PictureInPictureEvent.SetPipController }
|
||||||
|
pipEvents.assertTrue(1) { it is PictureInPictureEvent.EnterPictureInPicture }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun AndroidComposeUiTest<ComponentActivity>.setCallScreenView(
|
||||||
|
state: io.element.android.features.call.impl.ui.CallScreenState,
|
||||||
|
useInspectionMode: Boolean,
|
||||||
|
pipState: io.element.android.features.call.impl.pip.PictureInPictureState = aPictureInPictureState(supportPip = false),
|
||||||
|
) {
|
||||||
|
setContent {
|
||||||
|
// Inspection mode disables AndroidView creation; keep it configurable per test.
|
||||||
|
CompositionLocalProvider(LocalInspectionMode provides useInspectionMode) {
|
||||||
|
CallScreenView(
|
||||||
|
state = state,
|
||||||
|
pipState = pipState,
|
||||||
|
onConsoleMessage = {},
|
||||||
|
requestPermissions = { _, _ -> },
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implements(WebView::class)
|
||||||
|
internal class RecordingShadowWebView : ShadowWebView() {
|
||||||
|
companion object {
|
||||||
|
val dispatchedEvents = mutableListOf<KeyEvent>()
|
||||||
|
private var backHandlerJavascriptInterface: JavascriptBackHandler? = null
|
||||||
|
|
||||||
|
@Resetter
|
||||||
|
@JvmStatic
|
||||||
|
@Suppress("unused")
|
||||||
|
fun resetRecordedEvents() {
|
||||||
|
dispatchedEvents.clear()
|
||||||
|
backHandlerJavascriptInterface = null
|
||||||
|
}
|
||||||
|
|
||||||
|
fun invokeJavascriptBackHandler() {
|
||||||
|
val backHandler = checkNotNull(backHandlerJavascriptInterface) { "Expected backHandler JavaScript interface to be registered" }
|
||||||
|
backHandler.onBackPressed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
protected override fun addJavascriptInterface(`object`: Any, name: String) {
|
||||||
|
super.addJavascriptInterface(`object`, name)
|
||||||
|
if (name == "backHandler") {
|
||||||
|
backHandlerJavascriptInterface = `object` as? JavascriptBackHandler
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Implementation
|
||||||
|
@Suppress("unused")
|
||||||
|
fun dispatchKeyEvent(event: KeyEvent): Boolean {
|
||||||
|
dispatchedEvents += KeyEvent(event)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,45 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.call.ui
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.features.call.api.CallType
|
|
||||||
import io.element.android.features.call.impl.ui.getSessionId
|
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
|
||||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
|
||||||
import org.junit.Test
|
|
||||||
|
|
||||||
class CallTypeTest {
|
|
||||||
@Test
|
|
||||||
fun `getSessionId returns null for ExternalUrl`() {
|
|
||||||
assertThat(CallType.ExternalUrl("aURL").getSessionId()).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `getSessionId returns the sessionId for RoomCall`() {
|
|
||||||
assertThat(
|
|
||||||
CallType.RoomCall(
|
|
||||||
sessionId = A_SESSION_ID,
|
|
||||||
roomId = A_ROOM_ID,
|
|
||||||
isAudioCall = false,
|
|
||||||
).getSessionId()
|
|
||||||
).isEqualTo(A_SESSION_ID)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `ExternalUrl stringification does not contain the URL`() {
|
|
||||||
assertThat(CallType.ExternalUrl("aURL").toString()).isEqualTo("ExternalUrl")
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `RoomCall stringification does not contain the URL`() {
|
|
||||||
assertThat(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, false).toString())
|
|
||||||
.isEqualTo("RoomCall(sessionId=$A_SESSION_ID, roomId=$A_ROOM_ID, isAudioCall=false)")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,226 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2025 Element Creations Ltd.
|
|
||||||
* Copyright 2023-2025 New Vector Ltd.
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: AGPL-3.0-only OR LicenseRef-Element-Commercial.
|
|
||||||
* Please see LICENSE files in the repository root for full details.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package io.element.android.features.call.utils
|
|
||||||
|
|
||||||
import com.google.common.truth.Truth.assertThat
|
|
||||||
import io.element.android.features.call.impl.utils.CallIntentDataParser
|
|
||||||
import org.junit.Test
|
|
||||||
import org.junit.runner.RunWith
|
|
||||||
import org.robolectric.RobolectricTestRunner
|
|
||||||
import java.net.URLEncoder
|
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
|
||||||
class CallIntentDataParserTest {
|
|
||||||
private val callIntentDataParser = CallIntentDataParser()
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `a null data returns null`() {
|
|
||||||
val url: String? = null
|
|
||||||
assertThat(callIntentDataParser.parse(url)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `empty data returns null`() {
|
|
||||||
doTest("", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `invalid data returns null`() {
|
|
||||||
doTest("!", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `data with no scheme returns null`() {
|
|
||||||
doTest("test", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call http urls returns null`() {
|
|
||||||
doTest("http://call.element.io", null)
|
|
||||||
doTest("http://call.element.io/some-actual-call?with=parameters", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call urls with unknown host returns null`() {
|
|
||||||
// Check valid host first, should not return null
|
|
||||||
doTest("https://call.element.io", "https://call.element.io#?appPrompt=false&confineToRoom=true")
|
|
||||||
// Unknown host should return null
|
|
||||||
doTest("https://unknown.io", null)
|
|
||||||
doTest("https://call.unknown.io", null)
|
|
||||||
doTest("https://call.element.com", null)
|
|
||||||
doTest("https://call.element.io.tld", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call urls will be returned as is`() {
|
|
||||||
doTest(
|
|
||||||
url = "https://call.element.io",
|
|
||||||
expectedResult = "https://call.element.io#?$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url param gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = VALID_CALL_URL_WITH_PARAM,
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `HTTP and HTTPS urls that don't come from EC return null`() {
|
|
||||||
doTest("http://app.element.io", null)
|
|
||||||
doTest("https://app.element.io", null)
|
|
||||||
doTest("http://", null)
|
|
||||||
doTest("https://", null)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with no url returns null`() {
|
|
||||||
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
|
|
||||||
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
|
|
||||||
val url = "io.element.call:/?no_url=$encodedUrl"
|
|
||||||
assertThat(callIntentDataParser.parse(url)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `element scheme with no call host returns null`() {
|
|
||||||
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
|
|
||||||
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
|
|
||||||
val url = "element://no-call?url=$encodedUrl"
|
|
||||||
assertThat(callIntentDataParser.parse(url)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `element scheme with no data returns null`() {
|
|
||||||
val url = "element://call?url="
|
|
||||||
assertThat(callIntentDataParser.parse(url)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with no data returns null`() {
|
|
||||||
val url = "io.element.call:/?url="
|
|
||||||
assertThat(callIntentDataParser.parse(url)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `element invalid scheme returns null`() {
|
|
||||||
val embeddedUrl = VALID_CALL_URL_WITH_PARAM
|
|
||||||
val encodedUrl = URLEncoder.encode(embeddedUrl, "utf-8")
|
|
||||||
val url = "bad.scheme:/?url=$encodedUrl"
|
|
||||||
assertThat(callIntentDataParser.parse(url)).isNull()
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url extra param appPrompt gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM&appPrompt=true",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url extra param in fragment appPrompt gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=true",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&confineToRoom=true"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url extra param in fragment appPrompt and other gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=true&otherParam=maybe",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?appPrompt=false&otherParam=maybe&confineToRoom=true"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url extra param confineToRoom gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM&confineToRoom=false",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url extra param in fragment confineToRoom gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=false",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&appPrompt=false"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url extra param in fragment confineToRoom and more gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=false&otherParam=maybe",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?confineToRoom=true&otherParam=maybe&appPrompt=false"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url fragment gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#fragment",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url fragment with params gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#fragment?otherParam=maybe&$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with url fragment with other params gets url extracted`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?otherParam=maybe&$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with empty fragment`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
fun `Element Call url with empty fragment query`() {
|
|
||||||
doTest(
|
|
||||||
url = "$VALID_CALL_URL_WITH_PARAM#?",
|
|
||||||
expectedResult = "$VALID_CALL_URL_WITH_PARAM#?$EXTRA_PARAMS"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun doTest(url: String, expectedResult: String?) {
|
|
||||||
// Test direct parsing
|
|
||||||
assertThat(callIntentDataParser.parse(url)).isEqualTo(expectedResult)
|
|
||||||
|
|
||||||
// Test embedded url, scheme 1
|
|
||||||
val encodedUrl = URLEncoder.encode(url, "utf-8")
|
|
||||||
val urlScheme1 = "element://call?url=$encodedUrl"
|
|
||||||
assertThat(callIntentDataParser.parse(urlScheme1)).isEqualTo(expectedResult)
|
|
||||||
|
|
||||||
// Test embedded url, scheme 2
|
|
||||||
val urlScheme2 = "io.element.call:/?url=$encodedUrl"
|
|
||||||
assertThat(callIntentDataParser.parse(urlScheme2)).isEqualTo(expectedResult)
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val VALID_CALL_URL_WITH_PARAM = "https://call.element.io/some-actual-call?with=parameters"
|
|
||||||
const val EXTRA_PARAMS = "appPrompt=false&confineToRoom=true"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -13,7 +13,7 @@ import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.content.getSystemService
|
import androidx.core.content.getSystemService
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
import io.element.android.features.call.impl.notifications.RingingCallNotificationCreator
|
||||||
import io.element.android.features.call.impl.notifications.aCallNotificationData
|
import io.element.android.features.call.impl.notifications.aCallNotificationData
|
||||||
import io.element.android.features.call.impl.utils.ActiveCall
|
import io.element.android.features.call.impl.utils.ActiveCall
|
||||||
|
|
@ -77,7 +77,7 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
assertThat(manager.activeCall.value).isEqualTo(
|
assertThat(manager.activeCall.value).isEqualTo(
|
||||||
ActiveCall(
|
ActiveCall(
|
||||||
callType = CallType.RoomCall(
|
callData = CallData(
|
||||||
sessionId = callNotificationData.sessionId,
|
sessionId = callNotificationData.sessionId,
|
||||||
roomId = callNotificationData.roomId,
|
roomId = callNotificationData.roomId,
|
||||||
isAudioCall = false,
|
isAudioCall = false,
|
||||||
|
|
@ -104,7 +104,7 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
assertThat(manager.activeCall.value).isEqualTo(
|
assertThat(manager.activeCall.value).isEqualTo(
|
||||||
ActiveCall(
|
ActiveCall(
|
||||||
callType = CallType.RoomCall(
|
callData = CallData(
|
||||||
sessionId = callNotificationData.sessionId,
|
sessionId = callNotificationData.sessionId,
|
||||||
roomId = callNotificationData.roomId,
|
roomId = callNotificationData.roomId,
|
||||||
isAudioCall = true,
|
isAudioCall = true,
|
||||||
|
|
@ -132,7 +132,7 @@ class DefaultActiveCallManagerTest {
|
||||||
manager.registerIncomingCall(aCallNotificationData(roomId = A_ROOM_ID_2))
|
manager.registerIncomingCall(aCallNotificationData(roomId = A_ROOM_ID_2))
|
||||||
|
|
||||||
assertThat(manager.activeCall.value).isEqualTo(activeCall)
|
assertThat(manager.activeCall.value).isEqualTo(activeCall)
|
||||||
assertThat((manager.activeCall.value?.callType as? CallType.RoomCall)?.roomId).isNotEqualTo(A_ROOM_ID_2)
|
assertThat(manager.activeCall.value?.callData?.roomId).isNotEqualTo(A_ROOM_ID_2)
|
||||||
|
|
||||||
advanceTimeBy(1)
|
advanceTimeBy(1)
|
||||||
|
|
||||||
|
|
@ -178,7 +178,7 @@ class DefaultActiveCallManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `hangUpCall - removes existing call if the CallType matches`() = runTest {
|
fun `hangUpCall - removes existing call if the CallData matches`() = runTest {
|
||||||
setupShadowPowerManager()
|
setupShadowPowerManager()
|
||||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||||
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
||||||
|
|
@ -188,7 +188,7 @@ class DefaultActiveCallManagerTest {
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
assertThat(manager.activeCall.value).isNotNull()
|
||||||
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||||
|
|
||||||
manager.hangUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId, false))
|
manager.hangUpCall(CallData(notificationData.sessionId, notificationData.roomId, false))
|
||||||
assertThat(manager.activeCall.value).isNull()
|
assertThat(manager.activeCall.value).isNull()
|
||||||
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
assertThat(manager.activeWakeLock?.isHeld).isFalse()
|
||||||
|
|
||||||
|
|
@ -215,7 +215,7 @@ class DefaultActiveCallManagerTest {
|
||||||
val notificationData = aCallNotificationData(roomId = A_ROOM_ID)
|
val notificationData = aCallNotificationData(roomId = A_ROOM_ID)
|
||||||
manager.registerIncomingCall(notificationData)
|
manager.registerIncomingCall(notificationData)
|
||||||
|
|
||||||
manager.hangUpCall(CallType.RoomCall(notificationData.sessionId, notificationData.roomId, false))
|
manager.hangUpCall(CallData(notificationData.sessionId, notificationData.roomId, false))
|
||||||
|
|
||||||
coVerify {
|
coVerify {
|
||||||
room.declineCall(notificationEventId = notificationData.eventId)
|
room.declineCall(notificationEventId = notificationData.eventId)
|
||||||
|
|
@ -242,7 +242,7 @@ class DefaultActiveCallManagerTest {
|
||||||
val notificationData = aCallNotificationData(roomId = A_ROOM_ID)
|
val notificationData = aCallNotificationData(roomId = A_ROOM_ID)
|
||||||
// Do not register the incoming call, so the manager doesn't know about it
|
// Do not register the incoming call, so the manager doesn't know about it
|
||||||
manager.hangUpCall(
|
manager.hangUpCall(
|
||||||
callType = CallType.RoomCall(notificationData.sessionId, notificationData.roomId, false),
|
callData = CallData(notificationData.sessionId, notificationData.roomId, false),
|
||||||
notificationData = notificationData,
|
notificationData = notificationData,
|
||||||
)
|
)
|
||||||
coVerify {
|
coVerify {
|
||||||
|
|
@ -320,7 +320,7 @@ class DefaultActiveCallManagerTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `hangUpCall - does nothing if the CallType doesn't match`() = runTest {
|
fun `hangUpCall - does nothing if the CallData doesn't match`() = runTest {
|
||||||
setupShadowPowerManager()
|
setupShadowPowerManager()
|
||||||
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
val notificationManagerCompat = mockk<NotificationManagerCompat>(relaxed = true)
|
||||||
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
||||||
|
|
@ -329,7 +329,13 @@ class DefaultActiveCallManagerTest {
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
assertThat(manager.activeCall.value).isNotNull()
|
||||||
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||||
|
|
||||||
manager.hangUpCall(CallType.ExternalUrl("https://example.com"))
|
manager.hangUpCall(
|
||||||
|
CallData(
|
||||||
|
sessionId = A_SESSION_ID,
|
||||||
|
roomId = A_ROOM_ID_2,
|
||||||
|
isAudioCall = true,
|
||||||
|
)
|
||||||
|
)
|
||||||
assertThat(manager.activeCall.value).isNotNull()
|
assertThat(manager.activeCall.value).isNotNull()
|
||||||
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
assertThat(manager.activeWakeLock?.isHeld).isTrue()
|
||||||
|
|
||||||
|
|
@ -344,10 +350,10 @@ class DefaultActiveCallManagerTest {
|
||||||
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
val manager = createActiveCallManager(notificationManagerCompat = notificationManagerCompat)
|
||||||
assertThat(manager.activeCall.value).isNull()
|
assertThat(manager.activeCall.value).isNull()
|
||||||
|
|
||||||
manager.joinedCall(CallType.RoomCall(A_SESSION_ID, A_ROOM_ID, true))
|
manager.joinedCall(CallData(A_SESSION_ID, A_ROOM_ID, true))
|
||||||
assertThat(manager.activeCall.value).isEqualTo(
|
assertThat(manager.activeCall.value).isEqualTo(
|
||||||
ActiveCall(
|
ActiveCall(
|
||||||
callType = CallType.RoomCall(
|
callData = CallData(
|
||||||
sessionId = A_SESSION_ID,
|
sessionId = A_SESSION_ID,
|
||||||
roomId = A_ROOM_ID,
|
roomId = A_ROOM_ID,
|
||||||
isAudioCall = true,
|
isAudioCall = true,
|
||||||
|
|
@ -450,7 +456,7 @@ class DefaultActiveCallManagerTest {
|
||||||
|
|
||||||
assertThat(manager.activeCall.value).isEqualTo(
|
assertThat(manager.activeCall.value).isEqualTo(
|
||||||
ActiveCall(
|
ActiveCall(
|
||||||
callType = CallType.RoomCall(
|
callData = CallData(
|
||||||
sessionId = callNotificationData.sessionId,
|
sessionId = callNotificationData.sessionId,
|
||||||
roomId = callNotificationData.roomId,
|
roomId = callNotificationData.roomId,
|
||||||
isAudioCall = false,
|
isAudioCall = false,
|
||||||
|
|
|
||||||
|
|
@ -8,7 +8,7 @@
|
||||||
|
|
||||||
package io.element.android.features.call.utils
|
package io.element.android.features.call.utils
|
||||||
|
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.impl.notifications.CallNotificationData
|
import io.element.android.features.call.impl.notifications.CallNotificationData
|
||||||
import io.element.android.features.call.impl.utils.ActiveCall
|
import io.element.android.features.call.impl.utils.ActiveCall
|
||||||
import io.element.android.features.call.impl.utils.ActiveCallManager
|
import io.element.android.features.call.impl.utils.ActiveCallManager
|
||||||
|
|
@ -17,8 +17,8 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
|
||||||
class FakeActiveCallManager(
|
class FakeActiveCallManager(
|
||||||
var registerIncomingCallResult: (CallNotificationData) -> Unit = {},
|
var registerIncomingCallResult: (CallNotificationData) -> Unit = {},
|
||||||
var hangUpCallResult: (CallType, CallNotificationData?) -> Unit = { _, _ -> },
|
var hangUpCallResult: (CallData, CallNotificationData?) -> Unit = { _, _ -> },
|
||||||
var joinedCallResult: (CallType) -> Unit = {},
|
var joinedCallResult: (CallData) -> Unit = {},
|
||||||
) : ActiveCallManager {
|
) : ActiveCallManager {
|
||||||
override val activeCall = MutableStateFlow<ActiveCall?>(null)
|
override val activeCall = MutableStateFlow<ActiveCall?>(null)
|
||||||
|
|
||||||
|
|
@ -26,12 +26,12 @@ class FakeActiveCallManager(
|
||||||
registerIncomingCallResult(notificationData)
|
registerIncomingCallResult(notificationData)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun hangUpCall(callType: CallType, notificationData: CallNotificationData?) = simulateLongTask {
|
override suspend fun hangUpCall(callData: CallData, notificationData: CallNotificationData?) = simulateLongTask {
|
||||||
hangUpCallResult(callType, notificationData)
|
hangUpCallResult(callData, notificationData)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun joinedCall(callType: CallType) = simulateLongTask {
|
override suspend fun joinedCall(callData: CallData) = simulateLongTask {
|
||||||
joinedCallResult(callType)
|
joinedCallResult(callData)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun setActiveCall(value: ActiveCall?) {
|
fun setActiveCall(value: ActiveCall?) {
|
||||||
|
|
|
||||||
|
|
@ -8,16 +8,16 @@
|
||||||
|
|
||||||
package io.element.android.features.call.test
|
package io.element.android.features.call.test
|
||||||
|
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.api.ElementCallEntryPoint
|
import io.element.android.features.call.api.ElementCallEntryPoint
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
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.core.UserId
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakeElementCallEntryPoint(
|
class FakeElementCallEntryPoint(
|
||||||
var startCallResult: (CallType) -> Unit = { lambdaError() },
|
var startCallResult: (CallData) -> Unit = { lambdaError() },
|
||||||
var handleIncomingCallResult: (
|
var handleIncomingCallResult: (
|
||||||
CallType.RoomCall,
|
CallData,
|
||||||
EventId,
|
EventId,
|
||||||
UserId,
|
UserId,
|
||||||
String?,
|
String?,
|
||||||
|
|
@ -27,12 +27,12 @@ class FakeElementCallEntryPoint(
|
||||||
String?,
|
String?,
|
||||||
) -> Unit = { _, _, _, _, _, _, _, _ -> lambdaError() }
|
) -> Unit = { _, _, _, _, _, _, _, _ -> lambdaError() }
|
||||||
) : ElementCallEntryPoint {
|
) : ElementCallEntryPoint {
|
||||||
override fun startCall(callType: CallType) {
|
override fun startCall(callData: CallData) {
|
||||||
startCallResult(callType)
|
startCallResult(callData)
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun handleIncomingCall(
|
override suspend fun handleIncomingCall(
|
||||||
callType: CallType.RoomCall,
|
callData: CallData,
|
||||||
eventId: EventId,
|
eventId: EventId,
|
||||||
senderId: UserId,
|
senderId: UserId,
|
||||||
roomName: String?,
|
roomName: String?,
|
||||||
|
|
@ -44,7 +44,7 @@ class FakeElementCallEntryPoint(
|
||||||
textContent: String?,
|
textContent: String?,
|
||||||
) {
|
) {
|
||||||
handleIncomingCallResult(
|
handleIncomingCallResult(
|
||||||
callType,
|
callData,
|
||||||
eventId,
|
eventId,
|
||||||
senderId,
|
senderId,
|
||||||
roomName,
|
roomName,
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.logout.impl
|
package io.element.android.features.logout.impl
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.performTextInput
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.deactivation.impl.R
|
import io.element.android.features.deactivation.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -26,33 +29,29 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import io.element.android.tests.testutils.pressTag
|
import io.element.android.tests.testutils.pressTag
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class AccountDeactivationViewTest {
|
class AccountDeactivationViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes the expected callback`() {
|
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setAccountDeactivationView(
|
setAccountDeactivationView(
|
||||||
state = anAccountDeactivationState(eventSink = eventsRecorder),
|
state = anAccountDeactivationState(eventSink = eventsRecorder),
|
||||||
onBackClick = it,
|
onBackClick = it,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Deactivate emits the expected Event`() {
|
fun `clicking on Deactivate emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
||||||
rule.setAccountDeactivationView(
|
setAccountDeactivationView(
|
||||||
state = anAccountDeactivationState(
|
state = anAccountDeactivationState(
|
||||||
deactivateFormState = aDeactivateFormState(
|
deactivateFormState = aDeactivateFormState(
|
||||||
password = A_PASSWORD,
|
password = A_PASSWORD,
|
||||||
|
|
@ -60,14 +59,14 @@ class AccountDeactivationViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_delete)
|
clickOn(CommonStrings.action_delete)
|
||||||
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false))
|
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Deactivate on the confirmation dialog emits the expected Event`() {
|
fun `clicking on Deactivate on the confirmation dialog emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
||||||
rule.setAccountDeactivationView(
|
setAccountDeactivationView(
|
||||||
state = anAccountDeactivationState(
|
state = anAccountDeactivationState(
|
||||||
deactivateFormState = aDeactivateFormState(
|
deactivateFormState = aDeactivateFormState(
|
||||||
password = A_PASSWORD,
|
password = A_PASSWORD,
|
||||||
|
|
@ -76,14 +75,14 @@ class AccountDeactivationViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressTag(TestTags.dialogPositive.value)
|
pressTag(TestTags.dialogPositive.value)
|
||||||
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false))
|
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on retry on the confirmation dialog emits the expected Event`() {
|
fun `clicking on retry on the confirmation dialog emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
||||||
rule.setAccountDeactivationView(
|
setAccountDeactivationView(
|
||||||
state = anAccountDeactivationState(
|
state = anAccountDeactivationState(
|
||||||
deactivateFormState = aDeactivateFormState(
|
deactivateFormState = aDeactivateFormState(
|
||||||
password = A_PASSWORD,
|
password = A_PASSWORD,
|
||||||
|
|
@ -92,26 +91,26 @@ class AccountDeactivationViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_retry)
|
clickOn(CommonStrings.action_retry)
|
||||||
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(true))
|
eventsRecorder.assertSingle(AccountDeactivationEvents.DeactivateAccount(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `switching on the erase all switch emits the expected Event`() {
|
fun `switching on the erase all switch emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
||||||
rule.setAccountDeactivationView(
|
setAccountDeactivationView(
|
||||||
state = anAccountDeactivationState(
|
state = anAccountDeactivationState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_deactivate_account_delete_all_messages)
|
clickOn(R.string.screen_deactivate_account_delete_all_messages)
|
||||||
eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(true))
|
eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `switching off the erase all switch emits the expected Event`() {
|
fun `switching off the erase all switch emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
||||||
rule.setAccountDeactivationView(
|
setAccountDeactivationView(
|
||||||
state = anAccountDeactivationState(
|
state = anAccountDeactivationState(
|
||||||
deactivateFormState = aDeactivateFormState(
|
deactivateFormState = aDeactivateFormState(
|
||||||
eraseData = true,
|
eraseData = true,
|
||||||
|
|
@ -119,15 +118,15 @@ class AccountDeactivationViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_deactivate_account_delete_all_messages)
|
clickOn(R.string.screen_deactivate_account_delete_all_messages)
|
||||||
eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(false))
|
eventsRecorder.assertSingle(AccountDeactivationEvents.SetEraseData(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `typing text in the password field emits the expected Event`() {
|
fun `typing text in the password field emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
val eventsRecorder = EventsRecorder<AccountDeactivationEvents>()
|
||||||
rule.setAccountDeactivationView(
|
setAccountDeactivationView(
|
||||||
state = anAccountDeactivationState(
|
state = anAccountDeactivationState(
|
||||||
deactivateFormState = aDeactivateFormState(
|
deactivateFormState = aDeactivateFormState(
|
||||||
password = A_PASSWORD,
|
password = A_PASSWORD,
|
||||||
|
|
@ -135,12 +134,12 @@ class AccountDeactivationViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithTag(TestTags.loginPassword.value).performTextInput("A")
|
onNodeWithTag(TestTags.loginPassword.value).performTextInput("A")
|
||||||
eventsRecorder.assertSingle(AccountDeactivationEvents.SetPassword("A$A_PASSWORD"))
|
eventsRecorder.assertSingle(AccountDeactivationEvents.SetPassword("A$A_PASSWORD"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAccountDeactivationView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setAccountDeactivationView(
|
||||||
state: AccountDeactivationState,
|
state: AccountDeactivationState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.features.enterprise.api)
|
api(projects.features.enterprise.api)
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.compound)
|
implementation(projects.libraries.compound)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.forward.impl
|
package io.element.android.features.forward.impl
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -21,34 +24,30 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressTag
|
import io.element.android.tests.testutils.pressTag
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ForwardMessagesViewTest {
|
class ForwardMessagesViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cancel error emits the expected event`() {
|
fun `cancel error emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ForwardMessagesEvents>()
|
val eventsRecorder = EventsRecorder<ForwardMessagesEvents>()
|
||||||
rule.setForwardMessagesView(
|
setForwardMessagesView(
|
||||||
aForwardMessagesState(
|
aForwardMessagesState(
|
||||||
forwardAction = AsyncAction.Failure(AN_EXCEPTION),
|
forwardAction = AsyncAction.Failure(AN_EXCEPTION),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressTag(TestTags.dialogPositive.value)
|
pressTag(TestTags.dialogPositive.value)
|
||||||
eventsRecorder.assertSingle(ForwardMessagesEvents.ClearError)
|
eventsRecorder.assertSingle(ForwardMessagesEvents.ClearError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `success invokes onForwardSuccess`() {
|
fun `success invokes onForwardSuccess`() = runAndroidComposeUiTest {
|
||||||
val data = listOf(A_ROOM_ID)
|
val data = listOf(A_ROOM_ID)
|
||||||
val eventsRecorder = EventsRecorder<ForwardMessagesEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<ForwardMessagesEvents>(expectEvents = false)
|
||||||
ensureCalledOnceWithParam<List<RoomId>?>(data) { callback ->
|
ensureCalledOnceWithParam<List<RoomId>?>(data) { callback ->
|
||||||
rule.setForwardMessagesView(
|
setForwardMessagesView(
|
||||||
aForwardMessagesState(
|
aForwardMessagesState(
|
||||||
forwardAction = AsyncAction.Success(data),
|
forwardAction = AsyncAction.Success(data),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
|
|
@ -59,7 +58,7 @@ class ForwardMessagesViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setForwardMessagesView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setForwardMessagesView(
|
||||||
state: ForwardMessagesState,
|
state: ForwardMessagesState,
|
||||||
onForwardSuccess: (List<RoomId>) -> Unit = EnsureNeverCalledWithParam(),
|
onForwardSuccess: (List<RoomId>) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
package io.element.android.features.ftue.impl.sessionverification.choosemode
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.ftue.impl.R
|
import io.element.android.features.ftue.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
@ -18,65 +21,61 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ChooseSessionVerificationModeViewTest {
|
class ChooseSessionVerificationModeViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on learn more invokes the expected callback`() {
|
fun `clicking on learn more invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setChooseSelfVerificationModeView(
|
setChooseSelfVerificationModeView(
|
||||||
aChooseSelfVerificationModeState(),
|
aChooseSelfVerificationModeState(),
|
||||||
onLearnMoreClick = callback,
|
onLearnMoreClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_learn_more)
|
clickOn(CommonStrings.action_learn_more)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on use another device calls the callback`() {
|
fun `clicking on use another device calls the callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setChooseSelfVerificationModeView(
|
setChooseSelfVerificationModeView(
|
||||||
aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseAnotherDevice = true))),
|
aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseAnotherDevice = true))),
|
||||||
onUseAnotherDevice = callback,
|
onUseAnotherDevice = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_identity_use_another_device)
|
clickOn(R.string.screen_identity_use_another_device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on enter recovery key calls the callback`() {
|
fun `clicking on enter recovery key calls the callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setChooseSelfVerificationModeView(
|
setChooseSelfVerificationModeView(
|
||||||
aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseRecoveryKey = true))),
|
aChooseSelfVerificationModeState(AsyncData.Success(aButtonsState(canUseRecoveryKey = true))),
|
||||||
onEnterRecoveryKey = callback,
|
onEnterRecoveryKey = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_identity_confirmation_use_recovery_key)
|
clickOn(R.string.screen_identity_confirmation_use_recovery_key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on cannot confirm calls the reset keys callback`() {
|
fun `clicking on cannot confirm calls the reset keys callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setChooseSelfVerificationModeView(
|
setChooseSelfVerificationModeView(
|
||||||
aChooseSelfVerificationModeState(),
|
aChooseSelfVerificationModeState(),
|
||||||
onResetKey = callback,
|
onResetKey = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_identity_confirmation_cannot_confirm)
|
clickOn(R.string.screen_identity_confirmation_cannot_confirm)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChooseSelfVerificationModeView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setChooseSelfVerificationModeView(
|
||||||
state: ChooseSelfVerificationModeState,
|
state: ChooseSelfVerificationModeState,
|
||||||
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
onLearnMoreClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onUseAnotherDevice: () -> Unit = EnsureNeverCalled(),
|
onUseAnotherDevice: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -46,6 +46,7 @@ dependencies {
|
||||||
implementation(projects.libraries.permissions.noop)
|
implementation(projects.libraries.permissions.noop)
|
||||||
implementation(projects.libraries.preferences.api)
|
implementation(projects.libraries.preferences.api)
|
||||||
implementation(projects.libraries.push.api)
|
implementation(projects.libraries.push.api)
|
||||||
|
implementation(projects.libraries.sessionStorage.api)
|
||||||
implementation(projects.features.announcement.api)
|
implementation(projects.features.announcement.api)
|
||||||
implementation(projects.features.invite.api)
|
implementation(projects.features.invite.api)
|
||||||
implementation(projects.features.networkmonitor.api)
|
implementation(projects.features.networkmonitor.api)
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,13 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.home.impl.filters
|
package io.element.android.features.home.impl.filters
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.home.impl.R
|
import io.element.android.features.home.impl.R
|
||||||
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
|
import io.element.android.features.home.impl.filters.selection.FilterSelectionState
|
||||||
|
|
@ -17,23 +20,20 @@ import io.element.android.libraries.testtags.TestTags
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.pressTag
|
import io.element.android.tests.testutils.pressTag
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class RoomListFiltersViewTest {
|
class RoomListFiltersViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on filters generates expected Event`() {
|
fun `clicking on filters generates expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
|
val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
|
||||||
rule.setContent {
|
setContent {
|
||||||
RoomListFiltersView(
|
RoomListFiltersView(
|
||||||
state = aRoomListFiltersState(eventSink = eventsRecorder),
|
state = aRoomListFiltersState(eventSink = eventsRecorder),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.clickOn(R.string.screen_roomlist_filter_rooms)
|
clickOn(R.string.screen_roomlist_filter_rooms)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms),
|
RoomListFiltersEvent.ToggleFilter(RoomListFilter.Rooms),
|
||||||
|
|
@ -42,9 +42,9 @@ class RoomListFiltersViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on clear filters generates expected Event`() {
|
fun `clicking on clear filters generates expected Event`() = runAndroidComposeUiTest<ComponentActivity> {
|
||||||
val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
|
val eventsRecorder = EventsRecorder<RoomListFiltersEvent>()
|
||||||
rule.setContent {
|
setContent {
|
||||||
RoomListFiltersView(
|
RoomListFiltersView(
|
||||||
state = aRoomListFiltersState(
|
state = aRoomListFiltersState(
|
||||||
filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) },
|
filterSelectionStates = RoomListFilter.entries.map { FilterSelectionState(it, isSelected = true) },
|
||||||
|
|
@ -52,7 +52,7 @@ class RoomListFiltersViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.pressTag(TestTags.homeScreenClearFilters.value)
|
pressTag(TestTags.homeScreenClearFilters.value)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
RoomListFiltersEvent.ClearSelectedFilters,
|
RoomListFiltersEvent.ClearSelectedFilters,
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.home.impl.roomlist
|
package io.element.android.features.home.impl.roomlist
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.home.impl.R
|
import io.element.android.features.home.impl.R
|
||||||
import io.element.android.libraries.matrix.api.core.RoomId
|
import io.element.android.libraries.matrix.api.core.RoomId
|
||||||
|
|
@ -20,23 +23,20 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.setSafeContent
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class RoomListContextMenuTest {
|
class RoomListContextMenuTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Mark as read generates expected Events`() {
|
fun `clicking on Mark as read generates expected Events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val contextMenu = aContextMenuShown(hasNewContent = true)
|
val contextMenu = aContextMenuShown(hasNewContent = true)
|
||||||
rule.setRoomListContextMenu(
|
setRoomListContextMenu(
|
||||||
contextMenu = contextMenu,
|
contextMenu = contextMenu,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_roomlist_mark_as_read)
|
clickOn(R.string.screen_roomlist_mark_as_read)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
RoomListEvent.HideContextMenu,
|
RoomListEvent.HideContextMenu,
|
||||||
|
|
@ -46,14 +46,14 @@ class RoomListContextMenuTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Mark as unread generates expected Events`() {
|
fun `clicking on Mark as unread generates expected Events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val contextMenu = aContextMenuShown(hasNewContent = false)
|
val contextMenu = aContextMenuShown(hasNewContent = false)
|
||||||
rule.setRoomListContextMenu(
|
setRoomListContextMenu(
|
||||||
contextMenu = contextMenu,
|
contextMenu = contextMenu,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_roomlist_mark_as_unread)
|
clickOn(R.string.screen_roomlist_mark_as_unread)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
RoomListEvent.HideContextMenu,
|
RoomListEvent.HideContextMenu,
|
||||||
|
|
@ -63,14 +63,14 @@ class RoomListContextMenuTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Leave room generates expected Events`() {
|
fun `clicking on Leave room generates expected Events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val contextMenu = aContextMenuShown(isDm = false)
|
val contextMenu = aContextMenuShown(isDm = false)
|
||||||
rule.setRoomListContextMenu(
|
setRoomListContextMenu(
|
||||||
contextMenu = contextMenu,
|
contextMenu = contextMenu,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_leave_room)
|
clickOn(CommonStrings.action_leave_room)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
RoomListEvent.HideContextMenu,
|
RoomListEvent.HideContextMenu,
|
||||||
|
|
@ -80,48 +80,48 @@ class RoomListContextMenuTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Report room invokes the expected callback and generates expected Event`() {
|
fun `clicking on Report room invokes the expected callback and generates expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val contextMenu = aContextMenuShown()
|
val contextMenu = aContextMenuShown()
|
||||||
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
|
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
|
||||||
rule.setRoomListContextMenu(
|
setRoomListContextMenu(
|
||||||
contextMenu = contextMenu,
|
contextMenu = contextMenu,
|
||||||
canReportRoom = true,
|
canReportRoom = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
onRoomSettingsClick = EnsureNeverCalledWithParam(),
|
onRoomSettingsClick = EnsureNeverCalledWithParam(),
|
||||||
onReportRoomClick = callback,
|
onReportRoomClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_report_room)
|
clickOn(CommonStrings.action_report_room)
|
||||||
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
|
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
|
||||||
callback.assertSuccess()
|
callback.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Settings invokes the expected callback and generates expected Event`() {
|
fun `clicking on Settings invokes the expected callback and generates expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val contextMenu = aContextMenuShown()
|
val contextMenu = aContextMenuShown()
|
||||||
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
|
val callback = EnsureCalledOnceWithParam(contextMenu.roomId, Unit)
|
||||||
rule.setRoomListContextMenu(
|
setRoomListContextMenu(
|
||||||
contextMenu = contextMenu,
|
contextMenu = contextMenu,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
onRoomSettingsClick = callback,
|
onRoomSettingsClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_settings)
|
clickOn(CommonStrings.common_settings)
|
||||||
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
|
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
|
||||||
callback.assertSuccess()
|
callback.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Favourites generates expected Event`() {
|
fun `clicking on Favourites generates expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val contextMenu = aContextMenuShown(isDm = false, isFavorite = false)
|
val contextMenu = aContextMenuShown(isDm = false, isFavorite = false)
|
||||||
val callback = EnsureNeverCalledWithParam<RoomId>()
|
val callback = EnsureNeverCalledWithParam<RoomId>()
|
||||||
rule.setRoomListContextMenu(
|
setRoomListContextMenu(
|
||||||
contextMenu = contextMenu,
|
contextMenu = contextMenu,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
onRoomSettingsClick = callback,
|
onRoomSettingsClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_favourite)
|
clickOn(CommonStrings.common_favourite)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, true),
|
RoomListEvent.SetRoomIsFavorite(contextMenu.roomId, true),
|
||||||
|
|
@ -129,7 +129,7 @@ class RoomListContextMenuTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun AndroidComposeTestRule<*, *>.setRoomListContextMenu(
|
private fun AndroidComposeUiTest<ComponentActivity>.setRoomListContextMenu(
|
||||||
contextMenu: RoomListState.ContextMenu.Shown,
|
contextMenu: RoomListState.ContextMenu.Shown,
|
||||||
canReportRoom: Boolean = false,
|
canReportRoom: Boolean = false,
|
||||||
eventSink: (RoomListEvent) -> Unit,
|
eventSink: (RoomListEvent) -> Unit,
|
||||||
|
|
|
||||||
|
|
@ -6,10 +6,12 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.home.impl.roomlist
|
package io.element.android.features.home.impl.roomlist
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.home.impl.model.aRoomListRoomSummary
|
import io.element.android.features.home.impl.model.aRoomListRoomSummary
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -18,19 +20,16 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.setSafeContent
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class RoomListDeclineInviteMenuTest {
|
class RoomListDeclineInviteMenuTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on decline emits the expected Events`() {
|
fun `clicking on decline emits the expected Events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
||||||
rule.setSafeContent {
|
setSafeContent {
|
||||||
RoomListDeclineInviteMenu(
|
RoomListDeclineInviteMenu(
|
||||||
menu = menu,
|
menu = menu,
|
||||||
canReportRoom = false,
|
canReportRoom = false,
|
||||||
|
|
@ -38,7 +37,7 @@ class RoomListDeclineInviteMenuTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.clickOn(CommonStrings.action_decline)
|
clickOn(CommonStrings.action_decline)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
RoomListEvent.HideDeclineInviteMenu,
|
RoomListEvent.HideDeclineInviteMenu,
|
||||||
|
|
@ -48,10 +47,10 @@ class RoomListDeclineInviteMenuTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() {
|
fun `clicking on decline and block when canReportRoom=true, it emits the expected Events and callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
||||||
rule.setSafeContent {
|
setSafeContent {
|
||||||
RoomListDeclineInviteMenu(
|
RoomListDeclineInviteMenu(
|
||||||
menu = menu,
|
menu = menu,
|
||||||
canReportRoom = true,
|
canReportRoom = true,
|
||||||
|
|
@ -59,16 +58,16 @@ class RoomListDeclineInviteMenuTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.clickOn(CommonStrings.action_decline_and_block)
|
clickOn(CommonStrings.action_decline_and_block)
|
||||||
val expectedEvents = listOf(RoomListEvent.HideDeclineInviteMenu)
|
val expectedEvents = listOf(RoomListEvent.HideDeclineInviteMenu)
|
||||||
eventsRecorder.assertList(expectedEvents)
|
eventsRecorder.assertList(expectedEvents)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() {
|
fun `clicking on decline and block when canReportRoom=false, it emits the expected Events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
||||||
rule.setSafeContent {
|
setSafeContent {
|
||||||
RoomListDeclineInviteMenu(
|
RoomListDeclineInviteMenu(
|
||||||
menu = menu,
|
menu = menu,
|
||||||
canReportRoom = false,
|
canReportRoom = false,
|
||||||
|
|
@ -76,7 +75,7 @@ class RoomListDeclineInviteMenuTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.clickOn(CommonStrings.action_decline_and_block)
|
clickOn(CommonStrings.action_decline_and_block)
|
||||||
val expectedEvents = listOf(
|
val expectedEvents = listOf(
|
||||||
RoomListEvent.HideDeclineInviteMenu,
|
RoomListEvent.HideDeclineInviteMenu,
|
||||||
RoomListEvent.DeclineInvite(menu.roomSummary, blockUser = true),
|
RoomListEvent.DeclineInvite(menu.roomSummary, blockUser = true),
|
||||||
|
|
@ -85,10 +84,10 @@ class RoomListDeclineInviteMenuTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on cancel emits the expected Event`() {
|
fun `clicking on cancel emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
val menu = RoomListState.DeclineInviteMenu.Shown(roomSummary = aRoomListRoomSummary())
|
||||||
rule.setSafeContent {
|
setSafeContent {
|
||||||
RoomListDeclineInviteMenu(
|
RoomListDeclineInviteMenu(
|
||||||
menu = menu,
|
menu = menu,
|
||||||
canReportRoom = false,
|
canReportRoom = false,
|
||||||
|
|
@ -96,7 +95,7 @@ class RoomListDeclineInviteMenuTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertList(listOf(RoomListEvent.HideDeclineInviteMenu))
|
eventsRecorder.assertList(listOf(RoomListEvent.HideDeclineInviteMenu))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,19 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.home.impl.roomlist
|
package io.element.android.features.home.impl.roomlist
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.longClick
|
import androidx.compose.ui.test.longClick
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTouchInput
|
import androidx.compose.ui.test.performTouchInput
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.home.impl.HomeView
|
import io.element.android.features.home.impl.HomeView
|
||||||
import io.element.android.features.home.impl.R
|
import io.element.android.features.home.impl.R
|
||||||
|
|
@ -32,22 +35,17 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.setSafeContent
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class RoomListViewTest {
|
class RoomListViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() {
|
fun `displaying the view automatically sends a couple of UpdateVisibleRangeEvents`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = aRoomListState(
|
state = aRoomListState(
|
||||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -62,9 +60,9 @@ class RoomListViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on close recovery key banner emits the expected Event`() {
|
fun `clicking on close recovery key banner emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = aRoomListState(
|
state = aRoomListState(
|
||||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -74,15 +72,15 @@ class RoomListViewTest {
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val close = rule.activity.getString(CommonStrings.action_close)
|
val close = activity!!.getString(CommonStrings.action_close)
|
||||||
rule.onNodeWithContentDescription(close).performClick()
|
onNodeWithContentDescription(close).performClick()
|
||||||
eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
|
eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on close setup key banner emits the expected Event`() {
|
fun `clicking on close setup key banner emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = aRoomListState(
|
state = aRoomListState(
|
||||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
|
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -92,16 +90,16 @@ class RoomListViewTest {
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val close = rule.activity.getString(CommonStrings.action_close)
|
val close = activity!!.getString(CommonStrings.action_close)
|
||||||
rule.onNodeWithContentDescription(close).performClick()
|
onNodeWithContentDescription(close).performClick()
|
||||||
eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
|
eventsRecorder.assertSingle(RoomListEvent.DismissBanner)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on continue recovery key banner invokes the expected callback`() {
|
fun `clicking on continue recovery key banner invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = aRoomListState(
|
state = aRoomListState(
|
||||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.RecoveryKeyConfirmation),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -112,17 +110,17 @@ class RoomListViewTest {
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
|
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on continue setup key banner invokes the expected callback`() {
|
fun `clicking on continue setup key banner invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = aRoomListState(
|
state = aRoomListState(
|
||||||
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
|
contentState = aRoomsContentState(securityBannerState = SecurityBannerState.SetUpRecovery),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -131,28 +129,28 @@ class RoomListViewTest {
|
||||||
)
|
)
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
rule.clickOn(R.string.banner_set_up_recovery_submit)
|
clickOn(R.string.banner_set_up_recovery_submit)
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on start chat when the session has no room invokes the expected callback`() {
|
fun `clicking on start chat when the session has no room invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<RoomListEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = aRoomListState(
|
state = aRoomListState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
contentState = anEmptyContentState(),
|
contentState = anEmptyContentState(),
|
||||||
),
|
),
|
||||||
onCreateRoomClick = callback,
|
onCreateRoomClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_start_chat)
|
clickOn(CommonStrings.action_start_chat)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a room invokes the expected callback`() {
|
fun `clicking on a room invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val state = aRoomListState(
|
val state = aRoomListState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -161,7 +159,7 @@ class RoomListViewTest {
|
||||||
it.displayType == RoomSummaryDisplayType.ROOM
|
it.displayType == RoomSummaryDisplayType.ROOM
|
||||||
}
|
}
|
||||||
ensureCalledOnceWithParam(room0.roomId) { callback ->
|
ensureCalledOnceWithParam(room0.roomId) { callback ->
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomClick = callback,
|
onRoomClick = callback,
|
||||||
)
|
)
|
||||||
|
|
@ -169,14 +167,14 @@ class RoomListViewTest {
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.onNodeWithText(room0.latestEvent.content().toString()).performClick()
|
onNodeWithText(room0.latestEvent.content().toString()).performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a room twice invokes the expected callback only once`() {
|
fun `clicking on a room twice invokes the expected callback only once`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val state = aRoomListState(
|
val state = aRoomListState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -185,13 +183,13 @@ class RoomListViewTest {
|
||||||
it.displayType == RoomSummaryDisplayType.ROOM
|
it.displayType == RoomSummaryDisplayType.ROOM
|
||||||
}
|
}
|
||||||
ensureCalledOnceWithParam(room0.roomId) { callback ->
|
ensureCalledOnceWithParam(room0.roomId) { callback ->
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomClick = callback,
|
onRoomClick = callback,
|
||||||
)
|
)
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
rule.onNodeWithText(room0.latestEvent.content().toString())
|
onNodeWithText(room0.latestEvent.content().toString())
|
||||||
.performClick()
|
.performClick()
|
||||||
.performClick()
|
.performClick()
|
||||||
}
|
}
|
||||||
|
|
@ -199,7 +197,7 @@ class RoomListViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `long clicking on a room emits the expected Event`() {
|
fun `long clicking on a room emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val state = aRoomListState(
|
val state = aRoomListState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -207,18 +205,18 @@ class RoomListViewTest {
|
||||||
val room0 = state.contentAsRooms().summaries.first {
|
val room0 = state.contentAsRooms().summaries.first {
|
||||||
it.displayType == RoomSummaryDisplayType.ROOM
|
it.displayType == RoomSummaryDisplayType.ROOM
|
||||||
}
|
}
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() }
|
onNodeWithText(room0.latestEvent.content().toString()).performTouchInput { longClick() }
|
||||||
eventsRecorder.assertSingle(RoomListEvent.ShowContextMenu(room0))
|
eventsRecorder.assertSingle(RoomListEvent.ShowContextMenu(room0))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a room setting invokes the expected callback and emits expected Event`() {
|
fun `clicking on a room setting invokes the expected callback and emits expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val state = aRoomListState(
|
val state = aRoomListState(
|
||||||
contextMenu = aContextMenuShown(),
|
contextMenu = aContextMenuShown(),
|
||||||
|
|
@ -226,7 +224,7 @@ class RoomListViewTest {
|
||||||
)
|
)
|
||||||
val room0 = (state.contextMenu as RoomListState.ContextMenu.Shown).roomId
|
val room0 = (state.contextMenu as RoomListState.ContextMenu.Shown).roomId
|
||||||
ensureCalledOnceWithParam(room0) { callback ->
|
ensureCalledOnceWithParam(room0) { callback ->
|
||||||
rule.setRoomListView(
|
setRoomListView(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomSettingsClick = callback,
|
onRoomSettingsClick = callback,
|
||||||
)
|
)
|
||||||
|
|
@ -234,14 +232,14 @@ class RoomListViewTest {
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.clickOn(CommonStrings.common_settings)
|
clickOn(CommonStrings.common_settings)
|
||||||
}
|
}
|
||||||
|
|
||||||
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
|
eventsRecorder.assertSingle(RoomListEvent.HideContextMenu)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on accept and decline invite emits the expected Events`() {
|
fun `clicking on accept and decline invite emits the expected Events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
val eventsRecorder = EventsRecorder<RoomListEvent>()
|
||||||
val state = aRoomListState(
|
val state = aRoomListState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -249,13 +247,13 @@ class RoomListViewTest {
|
||||||
val invitedRoom = state.contentAsRooms().summaries.first {
|
val invitedRoom = state.contentAsRooms().summaries.first {
|
||||||
it.displayType == RoomSummaryDisplayType.INVITE
|
it.displayType == RoomSummaryDisplayType.INVITE
|
||||||
}
|
}
|
||||||
rule.setRoomListView(state = state)
|
setRoomListView(state = state)
|
||||||
|
|
||||||
// Remove automatic initial events
|
// Remove automatic initial events
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.clickOn(CommonStrings.action_accept)
|
clickOn(CommonStrings.action_accept)
|
||||||
rule.clickOn(CommonStrings.action_decline)
|
clickOn(CommonStrings.action_decline)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
RoomListEvent.AcceptInvite(invitedRoom),
|
RoomListEvent.AcceptInvite(invitedRoom),
|
||||||
|
|
@ -265,7 +263,7 @@ class RoomListViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setRoomListView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setRoomListView(
|
||||||
state: RoomListState,
|
state: RoomListState,
|
||||||
onRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
onRoomClick: (RoomId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onSettingsClick: () -> Unit = EnsureNeverCalled(),
|
onSettingsClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -5,34 +5,32 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.home.impl.spacefilters
|
package io.element.android.features.home.impl.spacefilters
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
import io.element.android.libraries.matrix.test.A_ROOM_ALIAS
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class SpaceFiltersViewTest {
|
class SpaceFiltersViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a filter with alias shows display name and alias`() {
|
fun `clicking on a filter with alias shows display name and alias`() = runAndroidComposeUiTest {
|
||||||
val filter = aSpaceServiceFilter(
|
val filter = aSpaceServiceFilter(
|
||||||
displayName = "Test Space",
|
displayName = "Test Space",
|
||||||
canonicalAlias = A_ROOM_ALIAS,
|
canonicalAlias = A_ROOM_ALIAS,
|
||||||
)
|
)
|
||||||
val eventsRecorder = EventsRecorder<SpaceFiltersEvent.Selecting>()
|
val eventsRecorder = EventsRecorder<SpaceFiltersEvent.Selecting>()
|
||||||
rule.setSpaceFiltersView(
|
setSpaceFiltersView(
|
||||||
state = aSelectingSpaceFiltersState(
|
state = aSelectingSpaceFiltersState(
|
||||||
availableFilters = listOf(filter),
|
availableFilters = listOf(filter),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -40,20 +38,20 @@ class SpaceFiltersViewTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Both display name and alias should be visible
|
// Both display name and alias should be visible
|
||||||
rule.onNodeWithText(filter.spaceRoom.displayName).assertExists()
|
onNodeWithText(filter.spaceRoom.displayName).assertExists()
|
||||||
rule.onNodeWithText(A_ROOM_ALIAS.value).assertExists()
|
onNodeWithText(A_ROOM_ALIAS.value).assertExists()
|
||||||
|
|
||||||
rule.onNodeWithText(filter.spaceRoom.displayName).performClick()
|
onNodeWithText(filter.spaceRoom.displayName).performClick()
|
||||||
|
|
||||||
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter))
|
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `multiple filters are displayed and clickable`() {
|
fun `multiple filters are displayed and clickable`() = runAndroidComposeUiTest {
|
||||||
val filter1 = aSpaceServiceFilter(displayName = "Space One")
|
val filter1 = aSpaceServiceFilter(displayName = "Space One")
|
||||||
val filter2 = aSpaceServiceFilter(displayName = "Space Two")
|
val filter2 = aSpaceServiceFilter(displayName = "Space Two")
|
||||||
val eventsRecorder = EventsRecorder<SpaceFiltersEvent.Selecting>()
|
val eventsRecorder = EventsRecorder<SpaceFiltersEvent.Selecting>()
|
||||||
rule.setSpaceFiltersView(
|
setSpaceFiltersView(
|
||||||
state = aSelectingSpaceFiltersState(
|
state = aSelectingSpaceFiltersState(
|
||||||
availableFilters = listOf(filter1, filter2),
|
availableFilters = listOf(filter1, filter2),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -61,17 +59,17 @@ class SpaceFiltersViewTest {
|
||||||
)
|
)
|
||||||
|
|
||||||
// Both filters should be visible
|
// Both filters should be visible
|
||||||
rule.onNodeWithText(filter1.spaceRoom.displayName).assertExists()
|
onNodeWithText(filter1.spaceRoom.displayName).assertExists()
|
||||||
rule.onNodeWithText(filter2.spaceRoom.displayName).assertExists()
|
onNodeWithText(filter2.spaceRoom.displayName).assertExists()
|
||||||
|
|
||||||
// Click on second filter
|
// Click on second filter
|
||||||
rule.onNodeWithText(filter2.spaceRoom.displayName).performClick()
|
onNodeWithText(filter2.spaceRoom.displayName).performClick()
|
||||||
|
|
||||||
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter2))
|
eventsRecorder.assertSingle(SpaceFiltersEvent.Selecting.SelectFilter(filter2))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSpaceFiltersView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setSpaceFiltersView(
|
||||||
state: SpaceFiltersState,
|
state: SpaceFiltersState,
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
|
|
|
||||||
|
|
@ -33,6 +33,7 @@ dependencies {
|
||||||
implementation(projects.libraries.architecture)
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrixui)
|
implementation(projects.libraries.matrixui)
|
||||||
|
implementation(projects.libraries.sessionStorage.api)
|
||||||
implementation(projects.libraries.designsystem)
|
implementation(projects.libraries.designsystem)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.services.analytics.api)
|
implementation(projects.services.analytics.api)
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.invite.impl.declineandblock
|
package io.element.android.features.invite.impl.declineandblock
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performTextInput
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.invite.impl.R
|
import io.element.android.features.invite.impl.R
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -21,98 +24,94 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class DeclineAndBlockViewTest {
|
class DeclineAndBlockViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invoke the expected callback`() {
|
fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setDeclineAndBlockView(
|
setDeclineAndBlockView(
|
||||||
aDeclineAndBlockState(
|
aDeclineAndBlockState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onBackClick = it
|
onBackClick = it
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on decline when enabled emits the expected event`() {
|
fun `clicking on decline when enabled emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
|
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
|
||||||
rule.setDeclineAndBlockView(
|
setDeclineAndBlockView(
|
||||||
aDeclineAndBlockState(
|
aDeclineAndBlockState(
|
||||||
blockUser = true,
|
blockUser = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_decline)
|
clickOn(CommonStrings.action_decline)
|
||||||
eventsRecorder.assertSingle(DeclineAndBlockEvents.Decline)
|
eventsRecorder.assertSingle(DeclineAndBlockEvents.Decline)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on decline when disabled does not emit event`() {
|
fun `clicking on decline when disabled does not emit event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>(expectEvents = false)
|
||||||
rule.setDeclineAndBlockView(
|
setDeclineAndBlockView(
|
||||||
aDeclineAndBlockState(
|
aDeclineAndBlockState(
|
||||||
blockUser = false,
|
blockUser = false,
|
||||||
reportRoom = false,
|
reportRoom = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_decline)
|
clickOn(CommonStrings.action_decline)
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on block option emits the expected event`() {
|
fun `clicking on block option emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
|
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
|
||||||
rule.setDeclineAndBlockView(
|
setDeclineAndBlockView(
|
||||||
aDeclineAndBlockState(
|
aDeclineAndBlockState(
|
||||||
blockUser = true,
|
blockUser = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_decline_and_block_block_user_option_title)
|
clickOn(R.string.screen_decline_and_block_block_user_option_title)
|
||||||
eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleBlockUser)
|
eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleBlockUser)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on report room option emits the expected event`() {
|
fun `clicking on report room option emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
|
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
|
||||||
rule.setDeclineAndBlockView(
|
setDeclineAndBlockView(
|
||||||
aDeclineAndBlockState(
|
aDeclineAndBlockState(
|
||||||
reportRoom = true,
|
reportRoom = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_report_room)
|
clickOn(CommonStrings.action_report_room)
|
||||||
eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleReportRoom)
|
eventsRecorder.assertSingle(DeclineAndBlockEvents.ToggleReportRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `typing text in the reason field emits the expected Event`() {
|
fun `typing text in the reason field emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
|
val eventsRecorder = EventsRecorder<DeclineAndBlockEvents>()
|
||||||
rule.setDeclineAndBlockView(
|
setDeclineAndBlockView(
|
||||||
aDeclineAndBlockState(
|
aDeclineAndBlockState(
|
||||||
reportRoom = true,
|
reportRoom = true,
|
||||||
reportReason = "",
|
reportReason = "",
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("").performTextInput("Spam!")
|
onNodeWithText("").performTextInput("Spam!")
|
||||||
eventsRecorder.assertSingle(DeclineAndBlockEvents.UpdateReportReason("Spam!"))
|
eventsRecorder.assertSingle(DeclineAndBlockEvents.UpdateReportReason("Spam!"))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setDeclineAndBlockView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setDeclineAndBlockView(
|
||||||
state: DeclineAndBlockState,
|
state: DeclineAndBlockState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation(libs.coroutines.core)
|
implementation(libs.coroutines.core)
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(projects.libraries.matrix.test)
|
implementation(projects.libraries.matrix.test)
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.joinroom.impl
|
package io.element.android.features.joinroom.impl
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.invite.api.InviteData
|
import io.element.android.features.invite.api.InviteData
|
||||||
import io.element.android.features.invite.test.anInviteData
|
import io.element.android.features.invite.test.anInviteData
|
||||||
|
|
@ -26,116 +29,112 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class JoinRoomViewTest {
|
class JoinRoomViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invoke the expected callback`() {
|
fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onBackClick = it
|
onBackClick = it
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Join room on CanJoin room emits the expected Event`() {
|
fun `clicking on Join room on CanJoin room emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanJoin),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_join_room_join_action)
|
clickOn(R.string.screen_join_room_join_action)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.JoinRoom)
|
eventsRecorder.assertSingle(JoinRoomEvents.JoinRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Knock room on CanKnock room emits the expected Event`() {
|
fun `clicking on Knock room on CanKnock room emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
||||||
knockMessage = "Knock knock",
|
knockMessage = "Knock knock",
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_join_room_knock_action)
|
clickOn(R.string.screen_join_room_knock_action)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.KnockRoom)
|
eventsRecorder.assertSingle(JoinRoomEvents.KnockRoom)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on closing Knock error emits the expected Event`() {
|
fun `clicking on closing Knock error emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
||||||
knockAction = AsyncAction.Failure(Exception("Error")),
|
knockAction = AsyncAction.Failure(Exception("Error")),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
|
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on cancel knock request emit the expected Event`() {
|
fun `clicking on cancel knock request emit the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_join_room_cancel_knock_action)
|
clickOn(R.string.screen_join_room_cancel_knock_action)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.CancelKnock(true))
|
eventsRecorder.assertSingle(JoinRoomEvents.CancelKnock(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on closing Cancel Knock error emits the expected Event`() {
|
fun `clicking on closing Cancel Knock error emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsKnocked),
|
||||||
cancelKnockAction = AsyncAction.Failure(Exception("Error")),
|
cancelKnockAction = AsyncAction.Failure(Exception("Error")),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
|
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on closing Join error emits the expected Event`() {
|
fun `clicking on closing Join error emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.CanKnock),
|
||||||
joinAction = AsyncAction.Failure(Exception("Error")),
|
joinAction = AsyncAction.Failure(Exception("Error")),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
|
eventsRecorder.assertSingle(JoinRoomEvents.ClearActionStates)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when joining room is successful, the expected callback is invoked`() {
|
fun `when joining room is successful, the expected callback is invoked`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
joinAction = AsyncAction.Success(Unit),
|
joinAction = AsyncAction.Success(Unit),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -146,53 +145,55 @@ class JoinRoomViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
|
fun `clicking on Accept when JoinAuthorisationStatus is IsInvited emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, null)),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, null)),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_accept)
|
clickOn(CommonStrings.action_accept)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.AcceptInvite(inviteData))
|
eventsRecorder.assertSingle(JoinRoomEvents.AcceptInvite(inviteData))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() {
|
fun `clicking on Decline when JoinAuthorisationStatus is IsInvited emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, null)),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, null)),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_decline)
|
clickOn(CommonStrings.action_decline)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, false))
|
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and can report room, the expected callback is invoked`() {
|
fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and can report room, the expected callback is invoked`() {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
runAndroidComposeUiTest {
|
||||||
val inviteData = anInviteData()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||||
val joinRoomState = aJoinRoomState(
|
val inviteData = anInviteData()
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, aRoomMember().toInviteSender())),
|
val joinRoomState = aJoinRoomState(
|
||||||
canReportRoom = true,
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsInvited(inviteData, aRoomMember().toInviteSender())),
|
||||||
eventSink = eventsRecorder,
|
canReportRoom = true,
|
||||||
)
|
eventSink = eventsRecorder,
|
||||||
ensureCalledOnceWithParam(inviteData) {
|
|
||||||
rule.setJoinRoomView(
|
|
||||||
state = joinRoomState,
|
|
||||||
onDeclineInviteAndBlockUser = it,
|
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_join_room_decline_and_block_button_title)
|
ensureCalledOnceWithParam(inviteData) {
|
||||||
|
setJoinRoomView(
|
||||||
|
state = joinRoomState,
|
||||||
|
onDeclineInviteAndBlockUser = it,
|
||||||
|
)
|
||||||
|
clickOn(R.string.screen_join_room_decline_and_block_button_title)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and cant report room, emits the expected Event`() {
|
fun `clicking on Decline and block when JoinAuthorisationStatus is IsInvited and cant report room, emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
val inviteData = anInviteData()
|
val inviteData = anInviteData()
|
||||||
val joinRoomState = aJoinRoomState(
|
val joinRoomState = aJoinRoomState(
|
||||||
|
|
@ -200,29 +201,29 @@ class JoinRoomViewTest {
|
||||||
canReportRoom = false,
|
canReportRoom = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
rule.setJoinRoomView(state = joinRoomState)
|
setJoinRoomView(state = joinRoomState)
|
||||||
rule.clickOn(R.string.screen_join_room_decline_and_block_button_title)
|
clickOn(R.string.screen_join_room_decline_and_block_button_title)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, true))
|
eventsRecorder.assertSingle(JoinRoomEvents.DeclineInvite(inviteData, true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Retry when an error occurs emits the expected Event`() {
|
fun `clicking on Retry when an error occurs emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aFailureContentState(),
|
contentState = aFailureContentState(),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_retry)
|
clickOn(CommonStrings.action_retry)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.RetryFetchingContent)
|
eventsRecorder.assertSingle(JoinRoomEvents.RetryFetchingContent)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on ok when user is unauthorized the expected callback`() {
|
fun `clicking on ok when user is unauthorized the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(),
|
contentState = aLoadedContentState(),
|
||||||
joinAction = AsyncAction.Failure(JoinRoom.Failures.UnauthorizedJoin),
|
joinAction = AsyncAction.Failure(JoinRoom.Failures.UnauthorizedJoin),
|
||||||
|
|
@ -230,25 +231,25 @@ class JoinRoomViewTest {
|
||||||
),
|
),
|
||||||
onBackClick = it
|
onBackClick = it
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on forget when user is banned invokes the expected callback`() {
|
fun `clicking on forget when user is banned invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
val eventsRecorder = EventsRecorder<JoinRoomEvents>()
|
||||||
rule.setJoinRoomView(
|
setJoinRoomView(
|
||||||
aJoinRoomState(
|
aJoinRoomState(
|
||||||
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(null, null)),
|
contentState = aLoadedContentState(joinAuthorisationStatus = JoinAuthorisationStatus.IsBanned(null, null)),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_join_room_forget_action)
|
clickOn(R.string.screen_join_room_forget_action)
|
||||||
eventsRecorder.assertSingle(JoinRoomEvents.ForgetRoom)
|
eventsRecorder.assertSingle(JoinRoomEvents.ForgetRoom)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setJoinRoomView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setJoinRoomView(
|
||||||
state: JoinRoomState,
|
state: JoinRoomState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onJoinSuccess: () -> Unit = EnsureNeverCalled(),
|
onJoinSuccess: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.knockrequests.impl.banner
|
package io.element.android.features.knockrequests.impl.banner
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.knockrequests.impl.R
|
import io.element.android.features.knockrequests.impl.R
|
||||||
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
||||||
|
|
@ -21,35 +24,30 @@ import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class KnockRequestsBannerViewTest {
|
class KnockRequestsBannerViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on view on single request invoke the expected callback`() {
|
fun `clicking on view on single request invoke the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setKnockRequestsBannerView(
|
setKnockRequestsBannerView(
|
||||||
state = aKnockRequestsBannerState(
|
state = aKnockRequestsBannerState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onViewRequestsClick = it
|
onViewRequestsClick = it
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_single_knock_request_view_button_title)
|
clickOn(R.string.screen_room_single_knock_request_view_button_title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on view all when multiple requests invoke the expected callback`() {
|
fun `clicking on view all when multiple requests invoke the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setKnockRequestsBannerView(
|
setKnockRequestsBannerView(
|
||||||
state = aKnockRequestsBannerState(
|
state = aKnockRequestsBannerState(
|
||||||
knockRequests = listOf(
|
knockRequests = listOf(
|
||||||
aKnockRequestPresentable(displayName = "Alice"),
|
aKnockRequestPresentable(displayName = "Alice"),
|
||||||
|
|
@ -60,37 +58,37 @@ class KnockRequestsBannerViewTest {
|
||||||
),
|
),
|
||||||
onViewRequestsClick = it
|
onViewRequestsClick = it
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title)
|
clickOn(R.string.screen_room_multiple_knock_requests_view_all_button_title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on accept on a single request emit the expected event`() {
|
fun `clicking on accept on a single request emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>()
|
||||||
rule.setKnockRequestsBannerView(
|
setKnockRequestsBannerView(
|
||||||
state = aKnockRequestsBannerState(
|
state = aKnockRequestsBannerState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_accept)
|
clickOn(CommonStrings.action_accept)
|
||||||
eventsRecorder.assertSingle(KnockRequestsBannerEvents.AcceptSingleRequest)
|
eventsRecorder.assertSingle(KnockRequestsBannerEvents.AcceptSingleRequest)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on dismiss emit the expected event`() {
|
fun `clicking on dismiss emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsBannerEvents>()
|
||||||
rule.setKnockRequestsBannerView(
|
setKnockRequestsBannerView(
|
||||||
state = aKnockRequestsBannerState(
|
state = aKnockRequestsBannerState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val close = rule.activity.getString(CommonStrings.action_close)
|
val close = activity!!.getString(CommonStrings.action_close)
|
||||||
rule.onNodeWithContentDescription(close).performClick()
|
onNodeWithContentDescription(close).performClick()
|
||||||
eventsRecorder.assertSingle(KnockRequestsBannerEvents.Dismiss)
|
eventsRecorder.assertSingle(KnockRequestsBannerEvents.Dismiss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setKnockRequestsBannerView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setKnockRequestsBannerView(
|
||||||
state: KnockRequestsBannerState,
|
state: KnockRequestsBannerState,
|
||||||
onViewRequestsClick: () -> Unit = EnsureNeverCalled(),
|
onViewRequestsClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.knockrequests.impl.list
|
package io.element.android.features.knockrequests.impl.list
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.knockrequests.impl.R
|
import io.element.android.features.knockrequests.impl.R
|
||||||
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
import io.element.android.features.knockrequests.impl.data.aKnockRequestPresentable
|
||||||
|
|
@ -23,90 +26,86 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class KnockRequestsListViewTest {
|
class KnockRequestsListViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invoke the expected callback`() {
|
fun `clicking on back invoke the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setKnockRequestsListView(
|
setKnockRequestsListView(
|
||||||
aKnockRequestsListState(
|
aKnockRequestsListState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onBackClick = it
|
onBackClick = it
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on accept emit the expected event`() {
|
fun `clicking on accept emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||||
val knockRequest = aKnockRequestPresentable()
|
val knockRequest = aKnockRequestPresentable()
|
||||||
rule.setKnockRequestsListView(
|
setKnockRequestsListView(
|
||||||
aKnockRequestsListState(
|
aKnockRequestsListState(
|
||||||
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_accept)
|
clickOn(CommonStrings.action_accept)
|
||||||
eventsRecorder.assertSingle(KnockRequestsListEvents.Accept(knockRequest))
|
eventsRecorder.assertSingle(KnockRequestsListEvents.Accept(knockRequest))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on decline emit the expected event`() {
|
fun `clicking on decline emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||||
val knockRequest = aKnockRequestPresentable()
|
val knockRequest = aKnockRequestPresentable()
|
||||||
rule.setKnockRequestsListView(
|
setKnockRequestsListView(
|
||||||
aKnockRequestsListState(
|
aKnockRequestsListState(
|
||||||
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_decline)
|
clickOn(CommonStrings.action_decline)
|
||||||
eventsRecorder.assertSingle(KnockRequestsListEvents.Decline(knockRequest))
|
eventsRecorder.assertSingle(KnockRequestsListEvents.Decline(knockRequest))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on decline and ban emit the expected event`() {
|
fun `clicking on decline and ban emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||||
val knockRequest = aKnockRequestPresentable()
|
val knockRequest = aKnockRequestPresentable()
|
||||||
rule.setKnockRequestsListView(
|
setKnockRequestsListView(
|
||||||
aKnockRequestsListState(
|
aKnockRequestsListState(
|
||||||
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
knockRequests = AsyncData.Success(persistentListOf(knockRequest)),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title)
|
clickOn(R.string.screen_knock_requests_list_decline_and_ban_action_title)
|
||||||
eventsRecorder.assertSingle(KnockRequestsListEvents.DeclineAndBan(knockRequest))
|
eventsRecorder.assertSingle(KnockRequestsListEvents.DeclineAndBan(knockRequest))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on accept all emit the expected event`() {
|
fun `clicking on accept all emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||||
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
||||||
rule.setKnockRequestsListView(
|
setKnockRequestsListView(
|
||||||
aKnockRequestsListState(
|
aKnockRequestsListState(
|
||||||
knockRequests = AsyncData.Success(knockRequests),
|
knockRequests = AsyncData.Success(knockRequests),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_knock_requests_list_accept_all_button_title)
|
clickOn(R.string.screen_knock_requests_list_accept_all_button_title)
|
||||||
eventsRecorder.assertSingle(KnockRequestsListEvents.AcceptAll)
|
eventsRecorder.assertSingle(KnockRequestsListEvents.AcceptAll)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `retry on async view retry emit the expected event`() {
|
fun `retry on async view retry emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||||
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
||||||
rule.setKnockRequestsListView(
|
setKnockRequestsListView(
|
||||||
aKnockRequestsListState(
|
aKnockRequestsListState(
|
||||||
knockRequests = AsyncData.Success(knockRequests),
|
knockRequests = AsyncData.Success(knockRequests),
|
||||||
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
|
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
|
||||||
|
|
@ -114,15 +113,15 @@ class KnockRequestsListViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_retry)
|
clickOn(CommonStrings.action_retry)
|
||||||
eventsRecorder.assertSingle(KnockRequestsListEvents.RetryCurrentAction)
|
eventsRecorder.assertSingle(KnockRequestsListEvents.RetryCurrentAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `canceling async view emit the expected event`() {
|
fun `canceling async view emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||||
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
||||||
rule.setKnockRequestsListView(
|
setKnockRequestsListView(
|
||||||
aKnockRequestsListState(
|
aKnockRequestsListState(
|
||||||
knockRequests = AsyncData.Success(knockRequests),
|
knockRequests = AsyncData.Success(knockRequests),
|
||||||
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
|
asyncAction = AsyncAction.Failure(RuntimeException("Failed to accept all")),
|
||||||
|
|
@ -130,15 +129,15 @@ class KnockRequestsListViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(KnockRequestsListEvents.ResetCurrentAction)
|
eventsRecorder.assertSingle(KnockRequestsListEvents.ResetCurrentAction)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `confirming async view emit the expected event`() {
|
fun `confirming async view emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
val eventsRecorder = EventsRecorder<KnockRequestsListEvents>()
|
||||||
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
val knockRequests = persistentListOf(aKnockRequestPresentable(), aKnockRequestPresentable())
|
||||||
rule.setKnockRequestsListView(
|
setKnockRequestsListView(
|
||||||
aKnockRequestsListState(
|
aKnockRequestsListState(
|
||||||
knockRequests = AsyncData.Success(knockRequests),
|
knockRequests = AsyncData.Success(knockRequests),
|
||||||
asyncAction = AsyncAction.ConfirmingNoParams,
|
asyncAction = AsyncAction.ConfirmingNoParams,
|
||||||
|
|
@ -146,12 +145,12 @@ class KnockRequestsListViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title)
|
clickOn(R.string.screen_knock_requests_list_accept_all_alert_confirm_button_title)
|
||||||
eventsRecorder.assertSingle(KnockRequestsListEvents.ConfirmCurrentAction)
|
eventsRecorder.assertSingle(KnockRequestsListEvents.ConfirmCurrentAction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setKnockRequestsListView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setKnockRequestsListView(
|
||||||
state: KnockRequestsListState,
|
state: KnockRequestsListState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -151,10 +151,7 @@ class LinkNewDeviceFlowNode(
|
||||||
LinkMobileStep.Starting -> {
|
LinkMobileStep.Starting -> {
|
||||||
// This step is not received at the moment, so do nothing
|
// This step is not received at the moment, so do nothing
|
||||||
}
|
}
|
||||||
LinkMobileStep.SyncingSecrets -> {
|
LinkMobileStep.SyncingSecrets -> Unit
|
||||||
// LinkMobileStep.Done is not received at the moment, so consider that the flow is done here
|
|
||||||
callback.onDone()
|
|
||||||
}
|
|
||||||
is LinkMobileStep.WaitingForAuth -> {
|
is LinkMobileStep.WaitingForAuth -> {
|
||||||
navigateToBrowser(linkMobileStep.verificationUri)
|
navigateToBrowser(linkMobileStep.verificationUri)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.linknewdevice.impl.screens.desktop
|
package io.element.android.features.linknewdevice.impl.screens.desktop
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.linknewdevice.impl.R
|
import io.element.android.features.linknewdevice.impl.R
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
|
@ -18,42 +21,37 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class DesktopNoticeViewTest {
|
class DesktopNoticeViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the expected callback`() {
|
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
state = aDesktopNoticeState(),
|
state = aDesktopNoticeState(),
|
||||||
onBackClicked = callback,
|
onBackClicked = callback,
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back button clicked - calls the expected callback`() {
|
fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
state = aDesktopNoticeState(),
|
state = aDesktopNoticeState(),
|
||||||
onBackClicked = callback,
|
onBackClicked = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when can continue - calls the expected callback`() {
|
fun `when can continue - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
state = aDesktopNoticeState(canContinue = true),
|
state = aDesktopNoticeState(canContinue = true),
|
||||||
onReadyToScanClick = callback,
|
onReadyToScanClick = callback,
|
||||||
)
|
)
|
||||||
|
|
@ -61,16 +59,16 @@ class DesktopNoticeViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on submit button clicked - emits the Continue event`() {
|
fun `on submit button clicked - emits the Continue event`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<DesktopNoticeEvent>()
|
val eventRecorder = EventsRecorder<DesktopNoticeEvent>()
|
||||||
rule.setView(
|
setView(
|
||||||
state = aDesktopNoticeState(eventSink = eventRecorder),
|
state = aDesktopNoticeState(eventSink = eventRecorder),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_link_new_device_desktop_submit)
|
clickOn(R.string.screen_link_new_device_desktop_submit)
|
||||||
eventRecorder.assertSingle(DesktopNoticeEvent.Continue)
|
eventRecorder.assertSingle(DesktopNoticeEvent.Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setView(
|
||||||
state: DesktopNoticeState,
|
state: DesktopNoticeState,
|
||||||
onBackClicked: () -> Unit = EnsureNeverCalled(),
|
onBackClicked: () -> Unit = EnsureNeverCalled(),
|
||||||
onReadyToScanClick: () -> Unit = EnsureNeverCalled(),
|
onReadyToScanClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -5,58 +5,56 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.linknewdevice.impl.screens.error
|
package io.element.android.features.linknewdevice.impl.screens.error
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ErrorViewTest {
|
class ErrorViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the onCancel callback`() {
|
fun `on back pressed - calls the onCancel callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setErrorView(
|
setErrorView(
|
||||||
onCancel = callback,
|
onCancel = callback,
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on try again button clicked - calls the expected callback`() {
|
fun `on try again button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setErrorView(
|
setErrorView(
|
||||||
onRetry = callback
|
onRetry = callback
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_try_again)
|
clickOn(CommonStrings.action_try_again)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on cancel button clicked - calls the expected callback`() {
|
fun `on cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setErrorView(
|
setErrorView(
|
||||||
onCancel = callback
|
onCancel = callback
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setErrorView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setErrorView(
|
||||||
onRetry: () -> Unit = EnsureNeverCalled(),
|
onRetry: () -> Unit = EnsureNeverCalled(),
|
||||||
onCancel: () -> Unit = EnsureNeverCalled(),
|
onCancel: () -> Unit = EnsureNeverCalled(),
|
||||||
errorScreenType: ErrorScreenType = ErrorScreenType.UnknownError,
|
errorScreenType: ErrorScreenType = ErrorScreenType.UnknownError,
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.linknewdevice.impl.screens.number
|
package io.element.android.features.linknewdevice.impl.screens.number
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.assertIsNotEnabled
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
|
@ -20,65 +23,60 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class EnterNumberViewTest {
|
class EnterNumberViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the expected callback`() {
|
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
state = aEnterNumberState(),
|
state = aEnterNumberState(),
|
||||||
onBackClicked = callback,
|
onBackClicked = callback,
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back button clicked - calls the expected callback`() {
|
fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
state = aEnterNumberState(),
|
state = aEnterNumberState(),
|
||||||
onBackClicked = callback,
|
onBackClicked = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on continue button clicked - emits the Continue event`() {
|
fun `on continue button clicked - emits the Continue event`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<EnterNumberEvent>()
|
val eventRecorder = EventsRecorder<EnterNumberEvent>()
|
||||||
rule.setView(
|
setView(
|
||||||
state = aEnterNumberState(
|
state = aEnterNumberState(
|
||||||
number = "12",
|
number = "12",
|
||||||
eventSink = eventRecorder,
|
eventSink = eventRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventRecorder.assertSingle(EnterNumberEvent.Continue)
|
eventRecorder.assertSingle(EnterNumberEvent.Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when the number is not complete, continue button is disabled`() {
|
fun `when the number is not complete, continue button is disabled`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<EnterNumberEvent>(expectEvents = false)
|
val eventRecorder = EventsRecorder<EnterNumberEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
state = aEnterNumberState(
|
state = aEnterNumberState(
|
||||||
number = "1",
|
number = "1",
|
||||||
eventSink = eventRecorder,
|
eventSink = eventRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val continueStr = rule.activity.getString(CommonStrings.action_continue)
|
val continueStr = activity!!.getString(CommonStrings.action_continue)
|
||||||
rule.onNodeWithText(continueStr).assertIsNotEnabled()
|
onNodeWithText(continueStr).assertIsNotEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setView(
|
||||||
state: EnterNumberState,
|
state: EnterNumberState,
|
||||||
onBackClicked: () -> Unit = EnsureNeverCalled(),
|
onBackClicked: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -5,36 +5,34 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.linknewdevice.impl.screens.qrcode
|
package io.element.android.features.linknewdevice.impl.screens.qrcode
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ShowQrCodeViewTest {
|
class ShowQrCodeViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the expected callback`() {
|
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
onBackClick = callback
|
onBackClick = callback
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setView(
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.linknewdevice.impl.screens.root
|
package io.element.android.features.linknewdevice.impl.screens.root
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.linknewdevice.impl.R
|
import io.element.android.features.linknewdevice.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
@ -19,74 +22,69 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LinkNewDeviceRootViewTest {
|
class LinkNewDeviceRootViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the onRetry callback`() {
|
fun `on back pressed - calls the onRetry callback`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setLinkNewDeviceRootView(
|
setLinkNewDeviceRootView(
|
||||||
state = aLinkNewDeviceRootState(
|
state = aLinkNewDeviceRootState(
|
||||||
eventSink = eventRecorder,
|
eventSink = eventRecorder,
|
||||||
),
|
),
|
||||||
onBackClick = callback
|
onBackClick = callback
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `link desktop button clicked - calls the expected callback`() {
|
fun `link desktop button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setLinkNewDeviceRootView(
|
setLinkNewDeviceRootView(
|
||||||
state = aLinkNewDeviceRootState(
|
state = aLinkNewDeviceRootState(
|
||||||
isSupported = AsyncData.Success(true),
|
isSupported = AsyncData.Success(true),
|
||||||
eventSink = eventRecorder,
|
eventSink = eventRecorder,
|
||||||
),
|
),
|
||||||
onLinkDesktopDeviceClick = callback,
|
onLinkDesktopDeviceClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_link_new_device_root_desktop_computer)
|
clickOn(R.string.screen_link_new_device_root_desktop_computer)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `link mobile button clicked - emits the expected event`() {
|
fun `link mobile button clicked - emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>()
|
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>()
|
||||||
rule.setLinkNewDeviceRootView(
|
setLinkNewDeviceRootView(
|
||||||
state = aLinkNewDeviceRootState(
|
state = aLinkNewDeviceRootState(
|
||||||
isSupported = AsyncData.Success(true),
|
isSupported = AsyncData.Success(true),
|
||||||
eventSink = eventRecorder,
|
eventSink = eventRecorder,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_link_new_device_root_mobile_device)
|
clickOn(R.string.screen_link_new_device_root_mobile_device)
|
||||||
eventRecorder.assertSingle(LinkNewDeviceRootEvent.LinkMobileDevice)
|
eventRecorder.assertSingle(LinkNewDeviceRootEvent.LinkMobileDevice)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `not supported - dismiss click - invokes the expected callback`() {
|
fun `not supported - dismiss click - invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
val eventRecorder = EventsRecorder<LinkNewDeviceRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setLinkNewDeviceRootView(
|
setLinkNewDeviceRootView(
|
||||||
state = aLinkNewDeviceRootState(
|
state = aLinkNewDeviceRootState(
|
||||||
isSupported = AsyncData.Success(false),
|
isSupported = AsyncData.Success(false),
|
||||||
eventSink = eventRecorder,
|
eventSink = eventRecorder,
|
||||||
),
|
),
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_dismiss)
|
clickOn(CommonStrings.action_dismiss)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setLinkNewDeviceRootView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setLinkNewDeviceRootView(
|
||||||
state: LinkNewDeviceRootState = aLinkNewDeviceRootState(),
|
state: LinkNewDeviceRootState = aLinkNewDeviceRootState(),
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onLinkDesktopDeviceClick: () -> Unit = EnsureNeverCalled(),
|
onLinkDesktopDeviceClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -5,11 +5,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.linknewdevice.impl.screens.scan
|
package io.element.android.features.linknewdevice.impl.screens.scan
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
import io.element.android.libraries.matrix.test.AN_EXCEPTION
|
||||||
|
|
@ -19,44 +22,39 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ScanQrCodeViewTest {
|
class ScanQrCodeViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the expected callback`() {
|
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<ScanQrCodeEvent>(expectEvents = false)
|
val eventRecorder = EventsRecorder<ScanQrCodeEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
state = aScanQrCodeState(
|
state = aScanQrCodeState(
|
||||||
eventSink = eventRecorder,
|
eventSink = eventRecorder,
|
||||||
),
|
),
|
||||||
onBackClick = callback
|
onBackClick = callback
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `try again button clicked - emits the expected event`() {
|
fun `try again button clicked - emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<ScanQrCodeEvent>()
|
val eventRecorder = EventsRecorder<ScanQrCodeEvent>()
|
||||||
rule.setView(
|
setView(
|
||||||
state = aScanQrCodeState(
|
state = aScanQrCodeState(
|
||||||
scanAction = AsyncAction.Failure(AN_EXCEPTION),
|
scanAction = AsyncAction.Failure(AN_EXCEPTION),
|
||||||
eventSink = eventRecorder,
|
eventSink = eventRecorder,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_try_again)
|
clickOn(CommonStrings.action_try_again)
|
||||||
eventRecorder.assertSingle(ScanQrCodeEvent.TryAgain)
|
eventRecorder.assertSingle(ScanQrCodeEvent.TryAgain)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setView(
|
||||||
state: ScanQrCodeState = aScanQrCodeState(),
|
state: ScanQrCodeState = aScanQrCodeState(),
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -5,15 +5,18 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.location.impl.share
|
package io.element.android.features.location.impl.share
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
|
@ -23,102 +26,98 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ShareLocationViewTest {
|
class ShareLocationViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test back action`() {
|
fun `test back action`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<ShareLocationEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setShareLocationView(
|
setShareLocationView(
|
||||||
state = aShareLocationState(
|
state = aShareLocationState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
navigateUp = callback,
|
navigateUp = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test fab click`() {
|
fun `test fab click`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||||
rule.setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
navigateUp = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick()
|
onNodeWithTag(TestTags.floatingActionButton.value).performClick()
|
||||||
eventsRecorder.assertSingle(ShareLocationEvent.StartTrackingUserLocation)
|
eventsRecorder.assertSingle(ShareLocationEvent.StartTrackingUserLocation)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when permission denied is displayed user can open the settings`() {
|
fun `when permission denied is displayed user can open the settings`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||||
rule.setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
navigateUp = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(ShareLocationEvent.OpenAppSettings)
|
eventsRecorder.assertSingle(ShareLocationEvent.OpenAppSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when permission denied is displayed user can close the dialog`() {
|
fun `when permission denied is displayed user can close the dialog`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||||
rule.setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionDenied),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
navigateUp = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when permission rationale is displayed user can request permissions`() {
|
fun `when permission rationale is displayed user can request permissions`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||||
rule.setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
navigateUp = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(ShareLocationEvent.RequestPermissions)
|
eventsRecorder.assertSingle(ShareLocationEvent.RequestPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when permission rationale is displayed user can close the dialog`() {
|
fun `when permission rationale is displayed user can close the dialog`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||||
rule.setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.PermissionRationale),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
navigateUp = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when location service disabled is displayed user can open location settings`() {
|
fun `when location service disabled is displayed user can open location settings`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||||
rule.setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||||
hasLocationPermission = true,
|
hasLocationPermission = true,
|
||||||
|
|
@ -126,14 +125,14 @@ class ShareLocationViewTest {
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
navigateUp = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(ShareLocationEvent.OpenLocationSettings)
|
eventsRecorder.assertSingle(ShareLocationEvent.OpenLocationSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when location service disabled is displayed user can close the dialog`() {
|
fun `when location service disabled is displayed user can close the dialog`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShareLocationEvent>()
|
||||||
rule.setShareLocationView(
|
setShareLocationView(
|
||||||
aShareLocationState(
|
aShareLocationState(
|
||||||
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
dialogState = ShareLocationState.Dialog.Constraints(LocationConstraintsDialogState.LocationServiceDisabled),
|
||||||
hasLocationPermission = true,
|
hasLocationPermission = true,
|
||||||
|
|
@ -141,12 +140,12 @@ class ShareLocationViewTest {
|
||||||
),
|
),
|
||||||
navigateUp = EnsureNeverCalled(),
|
navigateUp = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
eventsRecorder.assertSingle(ShareLocationEvent.DismissDialog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setShareLocationView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setShareLocationView(
|
||||||
state: ShareLocationState,
|
state: ShareLocationState,
|
||||||
navigateUp: () -> Unit = EnsureNeverCalled(),
|
navigateUp: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,19 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.location.impl.show
|
package io.element.android.features.location.impl.show
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.location.api.Location
|
import io.element.android.features.location.api.Location
|
||||||
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
import io.element.android.features.location.impl.common.ui.LocationConstraintsDialogState
|
||||||
|
|
@ -26,115 +29,111 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ShowLocationViewTest {
|
class ShowLocationViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test back action`() {
|
fun `test back action`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShowLocationEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<ShowLocationEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setShowLocationView(
|
setShowLocationView(
|
||||||
state = aShowLocationState(
|
state = aShowLocationState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test share action`() {
|
fun `test share action`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
||||||
rule.setShowLocationView(
|
setShowLocationView(
|
||||||
aShowLocationState(
|
aShowLocationState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = EnsureNeverCalled(),
|
onBackClick = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
val shareContentDescription = rule.activity.getString(CommonStrings.action_share)
|
val shareContentDescription = activity!!.getString(CommonStrings.action_share)
|
||||||
rule.onNodeWithContentDescription(shareContentDescription).performClick()
|
onNodeWithContentDescription(shareContentDescription).performClick()
|
||||||
// The default aStaticLocationMode uses Location(1.23, 2.34, 4f)
|
// The default aStaticLocationMode uses Location(1.23, 2.34, 4f)
|
||||||
eventsRecorder.assertSingle(ShowLocationEvent.Share(Location(1.23, 2.34, 4f)))
|
eventsRecorder.assertSingle(ShowLocationEvent.Share(Location(1.23, 2.34, 4f)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `test fab click`() {
|
fun `test fab click`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
||||||
rule.setShowLocationView(
|
setShowLocationView(
|
||||||
aShowLocationState(
|
aShowLocationState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = EnsureNeverCalled(),
|
onBackClick = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.onNodeWithTag(TestTags.floatingActionButton.value).performClick()
|
onNodeWithTag(TestTags.floatingActionButton.value).performClick()
|
||||||
eventsRecorder.assertSingle(ShowLocationEvent.TrackMyLocation(true))
|
eventsRecorder.assertSingle(ShowLocationEvent.TrackMyLocation(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when permission denied is displayed user can open the settings`() {
|
fun `when permission denied is displayed user can open the settings`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
||||||
rule.setShowLocationView(
|
setShowLocationView(
|
||||||
aShowLocationState(
|
aShowLocationState(
|
||||||
constraintsDialogState = LocationConstraintsDialogState.PermissionDenied,
|
constraintsDialogState = LocationConstraintsDialogState.PermissionDenied,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = EnsureNeverCalled(),
|
onBackClick = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(ShowLocationEvent.OpenAppSettings)
|
eventsRecorder.assertSingle(ShowLocationEvent.OpenAppSettings)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when permission denied is displayed user can close the dialog`() {
|
fun `when permission denied is displayed user can close the dialog`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
||||||
rule.setShowLocationView(
|
setShowLocationView(
|
||||||
aShowLocationState(
|
aShowLocationState(
|
||||||
constraintsDialogState = LocationConstraintsDialogState.PermissionDenied,
|
constraintsDialogState = LocationConstraintsDialogState.PermissionDenied,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = EnsureNeverCalled(),
|
onBackClick = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(ShowLocationEvent.DismissDialog)
|
eventsRecorder.assertSingle(ShowLocationEvent.DismissDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when permission rationale is displayed user can request permissions`() {
|
fun `when permission rationale is displayed user can request permissions`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
||||||
rule.setShowLocationView(
|
setShowLocationView(
|
||||||
aShowLocationState(
|
aShowLocationState(
|
||||||
constraintsDialogState = LocationConstraintsDialogState.PermissionRationale,
|
constraintsDialogState = LocationConstraintsDialogState.PermissionRationale,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = EnsureNeverCalled(),
|
onBackClick = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(ShowLocationEvent.RequestPermissions)
|
eventsRecorder.assertSingle(ShowLocationEvent.RequestPermissions)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when permission rationale is displayed user can close the dialog`() {
|
fun `when permission rationale is displayed user can close the dialog`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
val eventsRecorder = EventsRecorder<ShowLocationEvent>()
|
||||||
rule.setShowLocationView(
|
setShowLocationView(
|
||||||
aShowLocationState(
|
aShowLocationState(
|
||||||
constraintsDialogState = LocationConstraintsDialogState.PermissionRationale,
|
constraintsDialogState = LocationConstraintsDialogState.PermissionRationale,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = EnsureNeverCalled(),
|
onBackClick = EnsureNeverCalled(),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(ShowLocationEvent.DismissDialog)
|
eventsRecorder.assertSingle(ShowLocationEvent.DismissDialog)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setShowLocationView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setShowLocationView(
|
||||||
state: ShowLocationState,
|
state: ShowLocationState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.features.location.api)
|
api(projects.features.location.api)
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
implementation(libs.appyx.core)
|
|
||||||
implementation(projects.tests.testutils)
|
implementation(projects.tests.testutils)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,60 +6,57 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.lockscreen.impl.unlock.keypad
|
package io.element.android.features.lockscreen.impl.unlock.keypad
|
||||||
|
|
||||||
import android.view.KeyEvent
|
import android.view.KeyEvent
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.input.key.Key
|
import androidx.compose.ui.input.key.Key
|
||||||
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.ExperimentalTestApi
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.hasContentDescription
|
import androidx.compose.ui.test.hasContentDescription
|
||||||
import androidx.compose.ui.test.hasText
|
import androidx.compose.ui.test.hasText
|
||||||
import androidx.compose.ui.test.isRoot
|
import androidx.compose.ui.test.isRoot
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performKeyInput
|
import androidx.compose.ui.test.performKeyInput
|
||||||
import androidx.compose.ui.test.pressKey
|
import androidx.compose.ui.test.pressKey
|
||||||
import androidx.compose.ui.test.requestFocus
|
import androidx.compose.ui.test.requestFocus
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class PinKeypadTest {
|
class PinKeypadTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a number emits the expected event`() {
|
fun `clicking on a number emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PinKeypadModel>()
|
val eventsRecorder = EventsRecorder<PinKeypadModel>()
|
||||||
rule.setPinKeyPad(onClick = eventsRecorder)
|
setPinKeyPad(onClick = eventsRecorder)
|
||||||
rule.onNode(hasText("1")).performClick()
|
onNode(hasText("1")).performClick()
|
||||||
eventsRecorder.assertSingle(PinKeypadModel.Number('1'))
|
eventsRecorder.assertSingle(PinKeypadModel.Number('1'))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on the delete previous character button emits the expected event`() {
|
fun `clicking on the delete previous character button emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PinKeypadModel>()
|
val eventsRecorder = EventsRecorder<PinKeypadModel>()
|
||||||
rule.setPinKeyPad(onClick = eventsRecorder)
|
setPinKeyPad(onClick = eventsRecorder)
|
||||||
rule.onNode(hasContentDescription(rule.activity.getString(CommonStrings.a11y_delete))).performClick()
|
onNode(hasContentDescription(activity!!.getString(CommonStrings.a11y_delete))).performClick()
|
||||||
eventsRecorder.assertSingle(PinKeypadModel.Back)
|
eventsRecorder.assertSingle(PinKeypadModel.Back)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTestApi::class)
|
@OptIn(ExperimentalTestApi::class)
|
||||||
@Test
|
@Test
|
||||||
fun `typing using the hardware keyboard emits the expected events`() {
|
fun `typing using the hardware keyboard emits the expected events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PinKeypadModel>()
|
val eventsRecorder = EventsRecorder<PinKeypadModel>()
|
||||||
rule.setPinKeyPad(onClick = eventsRecorder)
|
setPinKeyPad(onClick = eventsRecorder)
|
||||||
rule.onNodeWithText("1").requestFocus()
|
onNodeWithText("1").requestFocus()
|
||||||
rule.onAllNodes(isRoot())[0].performKeyInput {
|
onAllNodes(isRoot())[0].performKeyInput {
|
||||||
val keys = listOf(
|
val keys = listOf(
|
||||||
Key.A,
|
Key.A,
|
||||||
Key.NumPad1,
|
Key.NumPad1,
|
||||||
|
|
@ -118,7 +115,7 @@ class PinKeypadTest {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPinKeyPad(
|
private fun AndroidComposeUiTest<ComponentActivity>.setPinKeyPad(
|
||||||
onClick: (PinKeypadModel) -> Unit = EnsureNeverCalledWithParam(),
|
onClick: (PinKeypadModel) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
package io.element.android.features.login.impl.screens.chooseaccountprovider
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
import io.element.android.features.login.impl.accountprovider.anAccountProvider
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
|
|
@ -25,36 +28,31 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ChooseAccountProviderViewTest {
|
class ChooseAccountProviderViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes the expected callback`() {
|
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<ChooseAccountProviderEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<ChooseAccountProviderEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setChooseAccountProviderView(
|
setChooseAccountProviderView(
|
||||||
state = aChooseAccountProviderState(
|
state = aChooseAccountProviderState(
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
onBackClick = it,
|
onBackClick = it,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `selecting an account provider emits the the expected event`() {
|
fun `selecting an account provider emits the the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<ChooseAccountProviderEvents>()
|
val eventSink = EventsRecorder<ChooseAccountProviderEvents>()
|
||||||
rule.setChooseAccountProviderView(
|
setChooseAccountProviderView(
|
||||||
state = aChooseAccountProviderState(
|
state = aChooseAccountProviderState(
|
||||||
accountProviders = listOf(
|
accountProviders = listOf(
|
||||||
ChooseAccountProviderPresenterTest.accountProvider1,
|
ChooseAccountProviderPresenterTest.accountProvider1,
|
||||||
|
|
@ -64,24 +62,24 @@ class ChooseAccountProviderViewTest {
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick()
|
onNodeWithText(ChooseAccountProviderPresenterTest.accountProvider1.title).performClick()
|
||||||
eventSink.assertSingle(ChooseAccountProviderEvents.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1))
|
eventSink.assertSingle(ChooseAccountProviderEvents.SelectAccountProvider(ChooseAccountProviderPresenterTest.accountProvider1))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when error is displayed - closing the dialog emits the expected event`() {
|
fun `when error is displayed - closing the dialog emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<ChooseAccountProviderEvents>()
|
val eventSink = EventsRecorder<ChooseAccountProviderEvents>()
|
||||||
rule.setChooseAccountProviderView(
|
setChooseAccountProviderView(
|
||||||
state = aChooseAccountProviderState(
|
state = aChooseAccountProviderState(
|
||||||
loginMode = AsyncData.Failure(AN_EXCEPTION),
|
loginMode = AsyncData.Failure(AN_EXCEPTION),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventSink.assertSingle(ChooseAccountProviderEvents.ClearError)
|
eventSink.assertSingle(ChooseAccountProviderEvents.ClearError)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setChooseAccountProviderView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setChooseAccountProviderView(
|
||||||
state: ChooseAccountProviderState,
|
state: ChooseAccountProviderState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(),
|
onOAuthDetails: (OAuthDetails) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
|
|
|
||||||
|
|
@ -6,20 +6,23 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.login.impl.screens.loginpassword
|
package io.element.android.features.login.impl.screens.loginpassword
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.assert
|
import androidx.compose.ui.test.assert
|
||||||
import androidx.compose.ui.test.assertIsEnabled
|
import androidx.compose.ui.test.assertIsEnabled
|
||||||
import androidx.compose.ui.test.assertIsNotEnabled
|
import androidx.compose.ui.test.assertIsNotEnabled
|
||||||
import androidx.compose.ui.test.hasText
|
import androidx.compose.ui.test.hasText
|
||||||
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.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTextInput
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.matrix.test.A_PASSWORD
|
import io.element.android.libraries.matrix.test.A_PASSWORD
|
||||||
import io.element.android.libraries.matrix.test.A_USER_NAME
|
import io.element.android.libraries.matrix.test.A_USER_NAME
|
||||||
|
|
@ -30,158 +33,154 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LoginPasswordViewTest {
|
class LoginPasswordViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invoke back callback`() {
|
fun `clicking on back invoke back callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `changing login invokes the expected event`() {
|
fun `changing login invokes the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val userNameHint = rule.activity.getString(CommonStrings.common_username)
|
val userNameHint = activity!!.getString(CommonStrings.common_username)
|
||||||
rule.onNodeWithText(userNameHint).performTextInput(A_USER_NAME)
|
onNodeWithText(userNameHint).performTextInput(A_USER_NAME)
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
LoginPasswordEvents.SetLogin(A_USER_NAME)
|
LoginPasswordEvents.SetLogin(A_USER_NAME)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `changing login removes new lines the expected event`() {
|
fun `changing login removes new lines the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val userNameHint = rule.activity.getString(CommonStrings.common_username)
|
val userNameHint = activity!!.getString(CommonStrings.common_username)
|
||||||
rule.onNodeWithText(userNameHint).performTextInput("a\nb")
|
onNodeWithText(userNameHint).performTextInput("a\nb")
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
LoginPasswordEvents.SetLogin("ab")
|
LoginPasswordEvents.SetLogin("ab")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clearing login invokes the expected event`() {
|
fun `clearing login invokes the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
formState = aLoginFormState(A_USER_NAME),
|
formState = aLoginFormState(A_USER_NAME),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val a11yClear = rule.activity.getString(CommonStrings.action_clear)
|
val a11yClear = activity!!.getString(CommonStrings.action_clear)
|
||||||
rule.onNodeWithContentDescription(a11yClear).performClick()
|
onNodeWithContentDescription(a11yClear).performClick()
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
LoginPasswordEvents.SetLogin("")
|
LoginPasswordEvents.SetLogin("")
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `changing password invokes the expected event`() {
|
fun `changing password invokes the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val userNameHint = rule.activity.getString(CommonStrings.common_password)
|
val userNameHint = activity!!.getString(CommonStrings.common_password)
|
||||||
rule.onNodeWithText(userNameHint).performTextInput(A_PASSWORD)
|
onNodeWithText(userNameHint).performTextInput(A_PASSWORD)
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
LoginPasswordEvents.SetPassword(A_PASSWORD)
|
LoginPasswordEvents.SetPassword(A_PASSWORD)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reveal password makes the password visible`() {
|
fun `reveal password makes the password visible`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
formState = aLoginFormState(password = A_PASSWORD),
|
formState = aLoginFormState(password = A_PASSWORD),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••"))
|
onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••"))
|
||||||
|
val resources = activity!!.resources
|
||||||
// Show password
|
// Show password
|
||||||
val a11yShowPassword = rule.activity.getString(CommonStrings.a11y_show_password)
|
val a11yShowPassword = resources.getString(CommonStrings.a11y_show_password)
|
||||||
rule.onNodeWithContentDescription(a11yShowPassword).performClick()
|
onNodeWithContentDescription(a11yShowPassword).performClick()
|
||||||
rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText(A_PASSWORD))
|
onNodeWithTag(TestTags.loginPassword.value).assert(hasText(A_PASSWORD))
|
||||||
// Hide password
|
// Hide password
|
||||||
val a11yHidePassword = rule.activity.getString(CommonStrings.a11y_hide_password)
|
val a11yHidePassword = resources.getString(CommonStrings.a11y_hide_password)
|
||||||
rule.onNodeWithContentDescription(a11yHidePassword).performClick()
|
onNodeWithContentDescription(a11yHidePassword).performClick()
|
||||||
rule.onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••"))
|
onNodeWithTag(TestTags.loginPassword.value).assert(hasText("••••••••"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when login is empty, continue button is not enabled`() {
|
fun `when login is empty, continue button is not enabled`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
formState = aLoginFormState(password = A_PASSWORD),
|
formState = aLoginFormState(password = A_PASSWORD),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val continueStr = rule.activity.getString(CommonStrings.action_continue)
|
val continueStr = activity!!.getString(CommonStrings.action_continue)
|
||||||
rule.onNodeWithText(continueStr).assertIsNotEnabled()
|
onNodeWithText(continueStr).assertIsNotEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when password is empty, continue button is not enabled`() {
|
fun `when password is empty, continue button is not enabled`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>(expectEvents = false)
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
formState = aLoginFormState(login = A_USER_NAME),
|
formState = aLoginFormState(login = A_USER_NAME),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val continueStr = rule.activity.getString(CommonStrings.action_continue)
|
val continueStr = activity!!.getString(CommonStrings.action_continue)
|
||||||
rule.onNodeWithText(continueStr).assertIsNotEnabled()
|
onNodeWithText(continueStr).assertIsNotEnabled()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Continue sends expected event`() {
|
fun `clicking on Continue sends expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
val eventsRecorder = EventsRecorder<LoginPasswordEvents>()
|
||||||
rule.setLoginPasswordView(
|
setLoginPasswordView(
|
||||||
aLoginPasswordState(
|
aLoginPasswordState(
|
||||||
formState = aLoginFormState(login = A_USER_NAME, password = A_PASSWORD),
|
formState = aLoginFormState(login = A_USER_NAME, password = A_PASSWORD),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val continueStr = rule.activity.getString(CommonStrings.action_continue)
|
val continueStr = activity!!.getString(CommonStrings.action_continue)
|
||||||
rule.onNodeWithText(continueStr).assertIsEnabled()
|
onNodeWithText(continueStr).assertIsEnabled()
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
LoginPasswordEvents.Submit
|
LoginPasswordEvents.Submit
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setLoginPasswordView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setLoginPasswordView(
|
||||||
state: LoginPasswordState,
|
state: LoginPasswordState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,17 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.login.impl.screens.onboarding
|
package io.element.android.features.login.impl.screens.onboarding
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import com.google.testing.junit.testparameterinjector.KotlinTestParameters.namedTestValues
|
import com.google.testing.junit.testparameterinjector.KotlinTestParameters.namedTestValues
|
||||||
import com.google.testing.junit.testparameterinjector.TestParameter
|
import com.google.testing.junit.testparameterinjector.TestParameter
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
|
|
@ -29,22 +32,17 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.RobolectricTestParameterInjector
|
import org.robolectric.RobolectricTestParameterInjector
|
||||||
|
|
||||||
@RunWith(RobolectricTestParameterInjector::class)
|
@RunWith(RobolectricTestParameterInjector::class)
|
||||||
class OnboardingViewTest {
|
class OnboardingViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when can create account - clicking on create account calls the expected callback`() {
|
fun `when can create account - clicking on create account calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
canCreateAccount = true,
|
canCreateAccount = true,
|
||||||
showDeveloperSettings = false,
|
showDeveloperSettings = false,
|
||||||
|
|
@ -52,40 +50,40 @@ class OnboardingViewTest {
|
||||||
),
|
),
|
||||||
onCreateAccount = callback,
|
onCreateAccount = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_onboarding_sign_up)
|
clickOn(R.string.screen_onboarding_sign_up)
|
||||||
// Developer settings should not be shown
|
// Developer settings should not be shown
|
||||||
val developerSettingsText = rule.activity.getString(CommonStrings.common_developer_options)
|
val developerSettingsText = activity!!.getString(CommonStrings.common_developer_options)
|
||||||
rule.onNodeWithContentDescription(developerSettingsText).assertDoesNotExist()
|
onNodeWithContentDescription(developerSettingsText).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when can go back - clicking on back calls the expected callback`() {
|
fun `when can go back - clicking on back calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
isAddingAccount = true,
|
isAddingAccount = true,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() {
|
fun `when can login with QR code - clicking on sign in with QR code calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
canLoginWithQrCode = true,
|
canLoginWithQrCode = true,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
onSignInWithQrCode = callback,
|
onSignInWithQrCode = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_onboarding_sign_in_with_qr_code)
|
clickOn(R.string.screen_onboarding_sign_in_with_qr_code)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,10 +93,10 @@ class OnboardingViewTest {
|
||||||
"can search account provider" to false,
|
"can search account provider" to false,
|
||||||
"cannot search account provider" to true,
|
"cannot search account provider" to true,
|
||||||
)
|
)
|
||||||
) {
|
) = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
||||||
ensureCalledOnceWithParam(mustChooseAccountProvider) { callback ->
|
ensureCalledOnceWithParam(mustChooseAccountProvider) { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
canLoginWithQrCode = true,
|
canLoginWithQrCode = true,
|
||||||
mustChooseAccountProvider = mustChooseAccountProvider,
|
mustChooseAccountProvider = mustChooseAccountProvider,
|
||||||
|
|
@ -106,7 +104,7 @@ class OnboardingViewTest {
|
||||||
),
|
),
|
||||||
onSignIn = callback,
|
onSignIn = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_onboarding_sign_in_manually)
|
clickOn(R.string.screen_onboarding_sign_in_manually)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -116,10 +114,10 @@ class OnboardingViewTest {
|
||||||
"can search account provider" to false,
|
"can search account provider" to false,
|
||||||
"cannot search account provider" to true,
|
"cannot search account provider" to true,
|
||||||
)
|
)
|
||||||
) {
|
) = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
||||||
ensureCalledOnceWithParam(mustChooseAccountProvider) { callback ->
|
ensureCalledOnceWithParam(mustChooseAccountProvider) { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
canLoginWithQrCode = false,
|
canLoginWithQrCode = false,
|
||||||
canCreateAccount = false,
|
canCreateAccount = false,
|
||||||
|
|
@ -128,89 +126,89 @@ class OnboardingViewTest {
|
||||||
),
|
),
|
||||||
onSignIn = callback,
|
onSignIn = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when sign in to pre defined account provider - clicking on button emits the expected event`() {
|
fun `when sign in to pre defined account provider - clicking on button emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>()
|
val eventSink = EventsRecorder<OnBoardingEvents>()
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
defaultAccountProvider = "element.io",
|
defaultAccountProvider = "element.io",
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val buttonText = rule.activity.getString(R.string.screen_onboarding_sign_in_to, "element.io")
|
val buttonText = activity!!.getString(R.string.screen_onboarding_sign_in_to, "element.io")
|
||||||
rule.onNodeWithText(buttonText).performClick()
|
onNodeWithText(buttonText).performClick()
|
||||||
eventSink.assertSingle(OnBoardingEvents.OnSignIn("element.io"))
|
eventSink.assertSingle(OnBoardingEvents.OnSignIn("element.io"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when error is displayed - closing the dialog emits the expected event`() {
|
fun `when error is displayed - closing the dialog emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>()
|
val eventSink = EventsRecorder<OnBoardingEvents>()
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
defaultAccountProvider = "element.io",
|
defaultAccountProvider = "element.io",
|
||||||
loginMode = AsyncData.Failure(AN_EXCEPTION),
|
loginMode = AsyncData.Failure(AN_EXCEPTION),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventSink.assertSingle(OnBoardingEvents.ClearError)
|
eventSink.assertSingle(OnBoardingEvents.ClearError)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on report a problem calls the sign in callback`() {
|
fun `clicking on report a problem calls the sign in callback`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
canReportBug = true,
|
canReportBug = true,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
onReportProblem = callback,
|
onReportProblem = callback,
|
||||||
)
|
)
|
||||||
val text = rule.activity.getString(CommonStrings.common_report_a_problem)
|
val text = activity!!.getString(CommonStrings.common_report_a_problem)
|
||||||
rule.onNodeWithText(text).assertExists()
|
onNodeWithText(text).assertExists()
|
||||||
rule.clickOn(CommonStrings.common_report_a_problem)
|
clickOn(CommonStrings.common_report_a_problem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on settings calls the developer settings callback`() {
|
fun `clicking on settings calls the developer settings callback`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
showDeveloperSettings = true,
|
showDeveloperSettings = true,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
onDeveloperSettingsClick = callback,
|
onDeveloperSettingsClick = callback,
|
||||||
)
|
)
|
||||||
val text = rule.activity.getString(CommonStrings.common_developer_options)
|
val text = activity!!.getString(CommonStrings.common_developer_options)
|
||||||
rule.onNodeWithContentDescription(text).performClick()
|
onNodeWithContentDescription(text).performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `cannot report a problem when the feature is disabled`() {
|
fun `cannot report a problem when the feature is disabled`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
val eventSink = EventsRecorder<OnBoardingEvents>(expectEvents = false)
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
canReportBug = false,
|
canReportBug = false,
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val text = rule.activity.getString(CommonStrings.common_report_a_problem)
|
val text = activity!!.getString(CommonStrings.common_report_a_problem)
|
||||||
rule.onNodeWithText(text).assertDoesNotExist()
|
onNodeWithText(text).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when success PasswordLogin - the expected callback is invoked and the event is received`() {
|
fun `when success PasswordLogin - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>()
|
val eventSink = EventsRecorder<OnBoardingEvents>()
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
loginMode = AsyncData.Success(LoginMode.PasswordLogin),
|
loginMode = AsyncData.Success(LoginMode.PasswordLogin),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
|
|
@ -222,11 +220,11 @@ class OnboardingViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when success Oidc - the expected callback is invoked and the event is received`() {
|
fun `when success Oidc - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>()
|
val eventSink = EventsRecorder<OnBoardingEvents>()
|
||||||
val oAuthDetails = OAuthDetails("aUrl")
|
val oAuthDetails = OAuthDetails("aUrl")
|
||||||
ensureCalledOnceWithParam(oAuthDetails) { callback ->
|
ensureCalledOnceWithParam(oAuthDetails) { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
loginMode = AsyncData.Success(LoginMode.OAuth(oAuthDetails)),
|
loginMode = AsyncData.Success(LoginMode.OAuth(oAuthDetails)),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
|
|
@ -238,11 +236,11 @@ class OnboardingViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when success AccountCreation - the expected callback is invoked and the event is received`() {
|
fun `when success AccountCreation - the expected callback is invoked and the event is received`() = runAndroidComposeUiTest {
|
||||||
val eventSink = EventsRecorder<OnBoardingEvents>()
|
val eventSink = EventsRecorder<OnBoardingEvents>()
|
||||||
val oAuthDetails = OAuthDetails("aUrl")
|
val oAuthDetails = OAuthDetails("aUrl")
|
||||||
ensureCalledOnceWithParam(oAuthDetails.url) { callback ->
|
ensureCalledOnceWithParam(oAuthDetails.url) { callback ->
|
||||||
rule.setOnboardingView(
|
setOnboardingView(
|
||||||
state = anOnBoardingState(
|
state = anOnBoardingState(
|
||||||
loginMode = AsyncData.Success(LoginMode.AccountCreation("aUrl")),
|
loginMode = AsyncData.Success(LoginMode.AccountCreation("aUrl")),
|
||||||
eventSink = eventSink,
|
eventSink = eventSink,
|
||||||
|
|
@ -253,7 +251,7 @@ class OnboardingViewTest {
|
||||||
eventSink.assertSingle(OnBoardingEvents.ClearError)
|
eventSink.assertSingle(OnBoardingEvents.ClearError)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setOnboardingView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setOnboardingView(
|
||||||
state: OnBoardingState,
|
state: OnBoardingState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onDeveloperSettingsClick: () -> Unit = EnsureNeverCalled(),
|
onDeveloperSettingsClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,49 +6,47 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.login.impl.screens.qrcode.confirmation
|
package io.element.android.features.login.impl.screens.qrcode.confirmation
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class QrCodeConfirmationViewTest {
|
class QrCodeConfirmationViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the expected callback`() {
|
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeConfirmationView(
|
setQrCodeConfirmationView(
|
||||||
step = QrCodeConfirmationStep.DisplayCheckCode("12"),
|
step = QrCodeConfirmationStep.DisplayCheckCode("12"),
|
||||||
onCancel = callback
|
onCancel = callback
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on Cancel button clicked - calls the expected callback`() {
|
fun `on Cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeConfirmationView(
|
setQrCodeConfirmationView(
|
||||||
step = QrCodeConfirmationStep.DisplayVerificationCode("123456"),
|
step = QrCodeConfirmationStep.DisplayVerificationCode("123456"),
|
||||||
onCancel = callback
|
onCancel = callback
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setQrCodeConfirmationView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setQrCodeConfirmationView(
|
||||||
step: QrCodeConfirmationStep,
|
step: QrCodeConfirmationStep,
|
||||||
onCancel: () -> Unit
|
onCancel: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.login.impl.screens.qrcode.error
|
package io.element.android.features.login.impl.screens.qrcode.error
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType
|
import io.element.android.features.login.impl.qrcode.QrCodeErrorScreenType
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -18,47 +21,42 @@ import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class QrCodeErrorViewTest {
|
class QrCodeErrorViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the onCancel callback`() {
|
fun `on back pressed - calls the onCancel callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeErrorView(
|
setQrCodeErrorView(
|
||||||
onCancel = callback,
|
onCancel = callback,
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on try again button clicked - calls the expected callback`() {
|
fun `on try again button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeErrorView(
|
setQrCodeErrorView(
|
||||||
onRetry = callback,
|
onRetry = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_try_again)
|
clickOn(CommonStrings.action_try_again)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on cancel button clicked - calls the expected callback`() {
|
fun `on cancel button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeErrorView(
|
setQrCodeErrorView(
|
||||||
onCancel = callback,
|
onCancel = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setQrCodeErrorView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setQrCodeErrorView(
|
||||||
onRetry: () -> Unit = EnsureNeverCalled(),
|
onRetry: () -> Unit = EnsureNeverCalled(),
|
||||||
onCancel: () -> Unit = EnsureNeverCalled(),
|
onCancel: () -> Unit = EnsureNeverCalled(),
|
||||||
errorScreenType: QrCodeErrorScreenType = QrCodeErrorScreenType.UnknownError,
|
errorScreenType: QrCodeErrorScreenType = QrCodeErrorScreenType.UnknownError,
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.login.impl.screens.qrcode.intro
|
package io.element.android.features.login.impl.screens.qrcode.intro
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.login.impl.R
|
import io.element.android.features.login.impl.R
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
|
@ -19,42 +22,37 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class QrCodeIntroViewTest {
|
class QrCodeIntroViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the expected callback`() {
|
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeIntroView(
|
setQrCodeIntroView(
|
||||||
state = aQrCodeIntroState(),
|
state = aQrCodeIntroState(),
|
||||||
onBackClicked = callback
|
onBackClicked = callback
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back button clicked - calls the expected callback`() {
|
fun `on back button clicked - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeIntroView(
|
setQrCodeIntroView(
|
||||||
state = aQrCodeIntroState(),
|
state = aQrCodeIntroState(),
|
||||||
onBackClicked = callback
|
onBackClicked = callback
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when can continue - calls the expected callback`() {
|
fun `when can continue - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeIntroView(
|
setQrCodeIntroView(
|
||||||
state = aQrCodeIntroState(canContinue = true),
|
state = aQrCodeIntroState(canContinue = true),
|
||||||
onContinue = callback
|
onContinue = callback
|
||||||
)
|
)
|
||||||
|
|
@ -62,16 +60,16 @@ class QrCodeIntroViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on submit button clicked - emits the Continue event`() {
|
fun `on submit button clicked - emits the Continue event`() = runAndroidComposeUiTest {
|
||||||
val eventRecorder = EventsRecorder<QrCodeIntroEvents>()
|
val eventRecorder = EventsRecorder<QrCodeIntroEvents>()
|
||||||
rule.setQrCodeIntroView(
|
setQrCodeIntroView(
|
||||||
state = aQrCodeIntroState(eventSink = eventRecorder),
|
state = aQrCodeIntroState(eventSink = eventRecorder),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_qr_code_login_initial_state_button_title)
|
clickOn(R.string.screen_qr_code_login_initial_state_button_title)
|
||||||
eventRecorder.assertSingle(QrCodeIntroEvents.Continue)
|
eventRecorder.assertSingle(QrCodeIntroEvents.Continue)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setQrCodeIntroView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setQrCodeIntroView(
|
||||||
state: QrCodeIntroState,
|
state: QrCodeIntroState,
|
||||||
onBackClicked: () -> Unit = EnsureNeverCalled(),
|
onBackClicked: () -> Unit = EnsureNeverCalled(),
|
||||||
onContinue: () -> Unit = EnsureNeverCalled(),
|
onContinue: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,15 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.login.impl.screens.qrcode.scan
|
package io.element.android.features.login.impl.screens.qrcode.scan
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.camera.lifecycle.ProcessCameraProvider
|
import androidx.camera.lifecycle.ProcessCameraProvider
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import androidx.test.platform.app.InstrumentationRegistry
|
import androidx.test.platform.app.InstrumentationRegistry
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -24,16 +27,11 @@ import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.After
|
import org.junit.After
|
||||||
import org.junit.Before
|
import org.junit.Before
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class QrCodeScanViewTest {
|
class QrCodeScanViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
private var provider: ProcessCameraProvider? = null
|
private var provider: ProcessCameraProvider? = null
|
||||||
|
|
||||||
@Before
|
@Before
|
||||||
|
|
@ -48,28 +46,28 @@ class QrCodeScanViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on back pressed - calls the expected callback`() {
|
fun `on back pressed - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setQrCodeScanView(
|
setQrCodeScanView(
|
||||||
state = aQrCodeScanState(),
|
state = aQrCodeScanState(),
|
||||||
onBackClick = callback
|
onBackClick = callback
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `on QR code data ready - calls the expected callback`() {
|
fun `on QR code data ready - calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
val data = FakeMatrixQrCodeLoginData()
|
val data = FakeMatrixQrCodeLoginData()
|
||||||
ensureCalledOnceWithParam<MatrixQrCodeLoginData>(data) { callback ->
|
ensureCalledOnceWithParam<MatrixQrCodeLoginData>(data) { callback ->
|
||||||
rule.setQrCodeScanView(
|
setQrCodeScanView(
|
||||||
state = aQrCodeScanState(authenticationAction = AsyncAction.Success(data)),
|
state = aQrCodeScanState(authenticationAction = AsyncAction.Success(data)),
|
||||||
onQrCodeDataReady = callback
|
onQrCodeDataReady = callback
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setQrCodeScanView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setQrCodeScanView(
|
||||||
state: QrCodeScanState,
|
state: QrCodeScanState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit = EnsureNeverCalledWithParam(),
|
onQrCodeDataReady: (MatrixQrCodeLoginData) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@ dependencies {
|
||||||
implementation(projects.libraries.testtags)
|
implementation(projects.libraries.testtags)
|
||||||
implementation(projects.libraries.uiStrings)
|
implementation(projects.libraries.uiStrings)
|
||||||
implementation(projects.libraries.dateformatter.api)
|
implementation(projects.libraries.dateformatter.api)
|
||||||
|
implementation(projects.libraries.sessionStorage.api)
|
||||||
implementation(projects.libraries.workmanager.api)
|
implementation(projects.libraries.workmanager.api)
|
||||||
api(projects.features.logout.api)
|
api(projects.features.logout.api)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.logout.impl
|
package io.element.android.features.logout.impl
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.testtags.TestTags
|
import io.element.android.libraries.testtags.TestTags
|
||||||
|
|
@ -21,97 +24,93 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import io.element.android.tests.testutils.pressTag
|
import io.element.android.tests.testutils.pressTag
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LogoutViewTest {
|
class LogoutViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on logout sends a LogoutEvents`() {
|
fun `clicking on logout sends a LogoutEvents`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LogoutEvents>()
|
val eventsRecorder = EventsRecorder<LogoutEvents>()
|
||||||
rule.setLogoutView(
|
setLogoutView(
|
||||||
aLogoutState(
|
aLogoutState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_signout)
|
clickOn(CommonStrings.action_signout)
|
||||||
eventsRecorder.assertSingle(LogoutEvents.Logout(false))
|
eventsRecorder.assertSingle(LogoutEvents.Logout(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `confirming logout sends a LogoutEvents`() {
|
fun `confirming logout sends a LogoutEvents`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LogoutEvents>()
|
val eventsRecorder = EventsRecorder<LogoutEvents>()
|
||||||
rule.setLogoutView(
|
setLogoutView(
|
||||||
aLogoutState(
|
aLogoutState(
|
||||||
logoutAction = AsyncAction.ConfirmingNoParams,
|
logoutAction = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressTag(TestTags.dialogPositive.value)
|
pressTag(TestTags.dialogPositive.value)
|
||||||
eventsRecorder.assertSingle(LogoutEvents.Logout(false))
|
eventsRecorder.assertSingle(LogoutEvents.Logout(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invoke back callback`() {
|
fun `clicking on back invoke back callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LogoutEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<LogoutEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setLogoutView(
|
setLogoutView(
|
||||||
aLogoutState(
|
aLogoutState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on confirm after error sends a LogoutEvents`() {
|
fun `clicking on confirm after error sends a LogoutEvents`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LogoutEvents>()
|
val eventsRecorder = EventsRecorder<LogoutEvents>()
|
||||||
rule.setLogoutView(
|
setLogoutView(
|
||||||
aLogoutState(
|
aLogoutState(
|
||||||
logoutAction = AsyncAction.Failure(Exception("Failed to logout")),
|
logoutAction = AsyncAction.Failure(Exception("Failed to logout")),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_signout_anyway)
|
clickOn(CommonStrings.action_signout_anyway)
|
||||||
eventsRecorder.assertSingle(LogoutEvents.Logout(true))
|
eventsRecorder.assertSingle(LogoutEvents.Logout(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on cancel after error sends a LogoutEvents`() {
|
fun `clicking on cancel after error sends a LogoutEvents`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LogoutEvents>()
|
val eventsRecorder = EventsRecorder<LogoutEvents>()
|
||||||
rule.setLogoutView(
|
setLogoutView(
|
||||||
aLogoutState(
|
aLogoutState(
|
||||||
logoutAction = AsyncAction.Failure(Exception("Failed to logout")),
|
logoutAction = AsyncAction.Failure(Exception("Failed to logout")),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(LogoutEvents.CloseDialogs)
|
eventsRecorder.assertSingle(LogoutEvents.CloseDialogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `last session setting button invoke onChangeRecoveryKeyClicked`() {
|
fun `last session setting button invoke onChangeRecoveryKeyClicked`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LogoutEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<LogoutEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setLogoutView(
|
setLogoutView(
|
||||||
aLogoutState(
|
aLogoutState(
|
||||||
isLastDevice = true,
|
isLastDevice = true,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onChangeRecoveryKeyClick = callback,
|
onChangeRecoveryKeyClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_settings)
|
clickOn(CommonStrings.common_settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setLogoutView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setLogoutView(
|
||||||
state: LogoutState,
|
state: LogoutState,
|
||||||
onChangeRecoveryKeyClick: () -> Unit = EnsureNeverCalled(),
|
onChangeRecoveryKeyClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.logout.impl.direct
|
package io.element.android.features.logout.impl.direct
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.logout.api.direct.DirectLogoutEvents
|
import io.element.android.features.logout.api.direct.DirectLogoutEvents
|
||||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||||
|
|
@ -21,83 +24,79 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.pressBackKey
|
import io.element.android.tests.testutils.pressBackKey
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class DefaultDirectLogoutViewTest {
|
class DefaultDirectLogoutViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on confirm logout sends expected Event`() {
|
fun `clicking on confirm logout sends expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
||||||
rule.setDefaultDirectLogoutView(
|
setDefaultDirectLogoutView(
|
||||||
state = aDirectLogoutState(
|
state = aDirectLogoutState(
|
||||||
logoutAction = AsyncAction.ConfirmingNoParams,
|
logoutAction = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_signout)
|
clickOn(CommonStrings.action_signout)
|
||||||
eventsRecorder.assertSingle(DirectLogoutEvents.Logout(false))
|
eventsRecorder.assertSingle(DirectLogoutEvents.Logout(false))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on cancel logout sends expected Event`() {
|
fun `clicking on cancel logout sends expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
||||||
rule.setDefaultDirectLogoutView(
|
setDefaultDirectLogoutView(
|
||||||
state = aDirectLogoutState(
|
state = aDirectLogoutState(
|
||||||
logoutAction = AsyncAction.ConfirmingNoParams,
|
logoutAction = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
|
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore("Pressing back key should dismiss the dialog, and so generate the expected event, but it's not the case.")
|
@Ignore("Pressing back key should dismiss the dialog, and so generate the expected event, but it's not the case.")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invoke back callback`() {
|
fun `clicking on back invoke back callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
||||||
rule.setDefaultDirectLogoutView(
|
setDefaultDirectLogoutView(
|
||||||
state = aDirectLogoutState(
|
state = aDirectLogoutState(
|
||||||
logoutAction = AsyncAction.ConfirmingNoParams,
|
logoutAction = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rule.pressBackKey()
|
pressBackKey()
|
||||||
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
|
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on confirm after error sends expected Event`() {
|
fun `clicking on confirm after error sends expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
||||||
rule.setDefaultDirectLogoutView(
|
setDefaultDirectLogoutView(
|
||||||
state = aDirectLogoutState(
|
state = aDirectLogoutState(
|
||||||
logoutAction = AsyncAction.Failure(Exception("Error")),
|
logoutAction = AsyncAction.Failure(Exception("Error")),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_signout_anyway)
|
clickOn(CommonStrings.action_signout_anyway)
|
||||||
eventsRecorder.assertSingle(DirectLogoutEvents.Logout(true))
|
eventsRecorder.assertSingle(DirectLogoutEvents.Logout(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on cancel after error sends expected Event`() {
|
fun `clicking on cancel after error sends expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
val eventsRecorder = EventsRecorder<DirectLogoutEvents>()
|
||||||
rule.setDefaultDirectLogoutView(
|
setDefaultDirectLogoutView(
|
||||||
state = aDirectLogoutState(
|
state = aDirectLogoutState(
|
||||||
logoutAction = AsyncAction.Failure(Exception("Error")),
|
logoutAction = AsyncAction.Failure(Exception("Error")),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
|
eventsRecorder.assertSingle(DirectLogoutEvents.CloseDialogs)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setDefaultDirectLogoutView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setDefaultDirectLogoutView(
|
||||||
state: DirectLogoutState,
|
state: DirectLogoutState,
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,7 @@ import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
import io.element.android.features.call.api.CallType
|
import io.element.android.features.call.api.CallData
|
||||||
import io.element.android.features.call.api.ElementCallEntryPoint
|
import io.element.android.features.call.api.ElementCallEntryPoint
|
||||||
import io.element.android.features.forward.api.ForwardEntryPoint
|
import io.element.android.features.forward.api.ForwardEntryPoint
|
||||||
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
|
import io.element.android.features.knockrequests.api.list.KnockRequestsListEntryPoint
|
||||||
|
|
@ -277,13 +277,13 @@ class MessagesFlowNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
|
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
|
||||||
val callType = CallType.RoomCall(
|
val callData = CallData(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
isAudioCall = isAudioCall
|
isAudioCall = isAudioCall,
|
||||||
)
|
)
|
||||||
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
||||||
elementCallEntryPoint.startCall(callType)
|
elementCallEntryPoint.startCall(callData)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToPinnedMessagesList() {
|
override fun navigateToPinnedMessagesList() {
|
||||||
|
|
@ -506,13 +506,13 @@ class MessagesFlowNode(
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
|
override fun navigateToRoomCall(roomId: RoomId, isAudioCall: Boolean) {
|
||||||
val callType = CallType.RoomCall(
|
val callData = CallData(
|
||||||
sessionId = sessionId,
|
sessionId = sessionId,
|
||||||
roomId = roomId,
|
roomId = roomId,
|
||||||
isAudioCall = isAudioCall
|
isAudioCall = isAudioCall
|
||||||
)
|
)
|
||||||
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
analyticsService.captureInteraction(Interaction.Name.MobileRoomCallButton)
|
||||||
elementCallEntryPoint.startCall(callType)
|
elementCallEntryPoint.startCall(callData)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) {
|
override fun navigateToThread(threadRootId: ThreadId, focusedEventId: EventId?) {
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,15 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl
|
package io.element.android.features.messages.impl
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.longClick
|
import androidx.compose.ui.test.longClick
|
||||||
import androidx.compose.ui.test.onAllNodesWithContentDescription
|
import androidx.compose.ui.test.onAllNodesWithContentDescription
|
||||||
import androidx.compose.ui.test.onAllNodesWithTag
|
import androidx.compose.ui.test.onAllNodesWithTag
|
||||||
|
|
@ -25,6 +27,7 @@ import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTouchInput
|
import androidx.compose.ui.test.performTouchInput
|
||||||
import androidx.compose.ui.test.swipeRight
|
import androidx.compose.ui.test.swipeRight
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.emojibasebindings.Emoji
|
import io.element.android.emojibasebindings.Emoji
|
||||||
|
|
@ -78,82 +81,78 @@ import io.element.android.tests.testutils.pressBack
|
||||||
import io.element.android.tests.testutils.setSafeContent
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.persistentMapOf
|
import kotlinx.collections.immutable.persistentMapOf
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
import kotlin.time.Duration.Companion.milliseconds
|
import kotlin.time.Duration.Companion.milliseconds
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class MessagesViewTest {
|
class MessagesViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invoke expected callback`() {
|
fun `clicking on back invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on room name invoke expected callback`() {
|
fun `clicking on room name invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onRoomDetailsClick = callback,
|
onRoomDetailsClick = callback,
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(state.roomName.orEmpty(), useUnmergedTree = true).performClick()
|
onNodeWithText(state.roomName.orEmpty(), useUnmergedTree = true).performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on join call invoke expected callback`() {
|
fun `clicking on join call invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
ensureCalledOnceWithParam(false) { callback ->
|
ensureCalledOnceWithParam(false) { callback ->
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onJoinCallClick = callback,
|
onJoinCallClick = callback,
|
||||||
)
|
)
|
||||||
val joinCallContentDescription = rule.activity.getString(CommonStrings.a11y_start_call)
|
val joinCallContentDescription = activity!!.getString(CommonStrings.a11y_start_call)
|
||||||
rule.onNodeWithContentDescription(joinCallContentDescription).performClick()
|
onNodeWithContentDescription(joinCallContentDescription).performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on join voice call invoke expected callback`() {
|
fun `clicking on join voice call invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
roomCallState = aStandByCallState(isDM = true)
|
roomCallState = aStandByCallState(isDM = true)
|
||||||
)
|
)
|
||||||
ensureCalledOnceWithParam(true) { callback ->
|
ensureCalledOnceWithParam(true) { callback ->
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onJoinCallClick = callback,
|
onJoinCallClick = callback,
|
||||||
)
|
)
|
||||||
val joinVoiceCallContentDescription = rule.activity.getString(CommonStrings.a11y_start_voice_call)
|
val joinVoiceCallContentDescription = activity!!.getString(CommonStrings.a11y_start_voice_call)
|
||||||
rule.onNodeWithContentDescription(joinVoiceCallContentDescription).performClick()
|
onNodeWithContentDescription(joinVoiceCallContentDescription).performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on an Event invoke expected callback`() {
|
fun `clicking on an Event invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(
|
timelineState = aTimelineState(
|
||||||
|
|
@ -167,12 +166,12 @@ class MessagesViewTest {
|
||||||
expectedParam2 = timelineItem,
|
expectedParam2 = timelineItem,
|
||||||
result = true,
|
result = true,
|
||||||
)
|
)
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onEventClick = callback,
|
onEventClick = callback,
|
||||||
)
|
)
|
||||||
// Cannot perform click on "Text", it's not detected. Use tag instead
|
// Cannot perform click on "Text", it's not detected. Use tag instead
|
||||||
rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performClick()
|
onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performClick()
|
||||||
callback.assertSuccess()
|
callback.assertSuccess()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -202,7 +201,7 @@ class MessagesViewTest {
|
||||||
userHasPermissionToRedactOther: Boolean = false,
|
userHasPermissionToRedactOther: Boolean = false,
|
||||||
userHasPermissionToSendReaction: Boolean = false,
|
userHasPermissionToSendReaction: Boolean = false,
|
||||||
userCanPinEvent: Boolean = false,
|
userCanPinEvent: Boolean = false,
|
||||||
) {
|
) = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ActionListEvent>()
|
val eventsRecorder = EventsRecorder<ActionListEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
actionListState = anActionListState(
|
actionListState = anActionListState(
|
||||||
|
|
@ -220,11 +219,11 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
// Cannot perform click on "Text", it's not detected. Use tag instead
|
// Cannot perform click on "Text", it's not detected. Use tag instead
|
||||||
rule.onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() }
|
onAllNodesWithTag(TestTags.messageBubble.value).onFirst().performTouchInput { longClick() }
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
ActionListEvent.ComputeForMessage(
|
ActionListEvent.ComputeForMessage(
|
||||||
event = timelineItem,
|
event = timelineItem,
|
||||||
|
|
@ -235,7 +234,7 @@ class MessagesViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
fun `clicking on a read receipt list emits the expected Event`() {
|
fun `clicking on a read receipt list emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ReadReceiptBottomSheetEvent>()
|
val eventsRecorder = EventsRecorder<ReadReceiptBottomSheetEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(
|
timelineState = aTimelineState(
|
||||||
|
|
@ -255,10 +254,10 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick()
|
onNodeWithTag(TestTags.messageReadReceipts.value, useUnmergedTree = true).performClick()
|
||||||
eventsRecorder.assertSingle(ReadReceiptBottomSheetEvent.EventSelected(timelineItem))
|
eventsRecorder.assertSingle(ReadReceiptBottomSheetEvent.EventSelected(timelineItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -272,7 +271,7 @@ class MessagesViewTest {
|
||||||
swipeTest(userHasPermissionToSendMessage = false)
|
swipeTest(userHasPermissionToSendMessage = false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun swipeTest(userHasPermissionToSendMessage: Boolean) {
|
private fun swipeTest(userHasPermissionToSendMessage: Boolean) = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
||||||
val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true)
|
val canBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = true)
|
||||||
val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false)
|
val cannotBeRepliedEvent = aTimelineItemEvent(canBeRepliedTo = false)
|
||||||
|
|
@ -285,10 +284,10 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.onAllNodesWithTag(TestTags.messageBubble.value).apply {
|
onAllNodesWithTag(TestTags.messageBubble.value).apply {
|
||||||
onFirst().performTouchInput { swipeRight(endX = 200f) }
|
onFirst().performTouchInput { swipeRight(endX = 200f) }
|
||||||
onLast().performTouchInput { swipeRight(endX = 200f) }
|
onLast().performTouchInput { swipeRight(endX = 200f) }
|
||||||
}
|
}
|
||||||
|
|
@ -300,7 +299,7 @@ class MessagesViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on send location invoke expected callback`() {
|
fun `clicking on send location invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
composerState = aMessageComposerState(
|
composerState = aMessageComposerState(
|
||||||
|
|
@ -309,16 +308,16 @@ class MessagesViewTest {
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onSendLocationClick = callback,
|
onSendLocationClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_room_attachment_source_location)
|
clickOn(R.string.screen_room_attachment_source_location)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on create poll invoke expected callback`() {
|
fun `clicking on create poll invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
composerState = aMessageComposerState(
|
composerState = aMessageComposerState(
|
||||||
|
|
@ -327,25 +326,25 @@ class MessagesViewTest {
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onCreatePollClick = callback,
|
onCreatePollClick = callback,
|
||||||
)
|
)
|
||||||
// Then click on the poll action
|
// Then click on the poll action
|
||||||
rule.clickOn(R.string.screen_room_attachment_source_poll)
|
clickOn(R.string.screen_room_attachment_source_poll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
fun `clicking on the avatar of the sender of an Event emits the expected event`() {
|
fun `clicking on the avatar of the sender of an Event emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
val timelineEvent = state.timelineState.timelineItems.filterIsInstance<TimelineItem.Event>().first()
|
val timelineEvent = state.timelineState.timelineItems.filterIsInstance<TimelineItem.Event>().first()
|
||||||
rule.setMessagesView(state = state)
|
setMessagesView(state = state)
|
||||||
rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
|
onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
MessagesEvent.OnUserClicked(
|
MessagesEvent.OnUserClicked(
|
||||||
MatrixUser(
|
MatrixUser(
|
||||||
|
|
@ -359,12 +358,12 @@ class MessagesViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
fun `clicking on the display name of the sender of an Event emits expected event`() {
|
fun `clicking on the display name of the sender of an Event emits expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
||||||
val state = aMessagesState(eventSink = eventsRecorder)
|
val state = aMessagesState(eventSink = eventsRecorder)
|
||||||
val timelineEvent = state.timelineState.timelineItems.filterIsInstance<TimelineItem.Event>().first()
|
val timelineEvent = state.timelineState.timelineItems.filterIsInstance<TimelineItem.Event>().first()
|
||||||
rule.setMessagesView(state = state)
|
setMessagesView(state = state)
|
||||||
rule.onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
|
onNodeWithTag(TestTags.timelineItemSenderAvatar.value, useUnmergedTree = true).performClick()
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
MessagesEvent.OnUserClicked(
|
MessagesEvent.OnUserClicked(
|
||||||
MatrixUser(
|
MatrixUser(
|
||||||
|
|
@ -377,7 +376,7 @@ class MessagesViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `selecting a action on a message emits the expected Event`() {
|
fun `selecting a action on a message emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
|
|
@ -395,17 +394,17 @@ class MessagesViewTest {
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = stateWithMessageAction,
|
state = stateWithMessageAction,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_edit)
|
clickOn(CommonStrings.action_edit)
|
||||||
// Give time for the close animation to complete
|
// Give time for the close animation to complete
|
||||||
rule.mainClock.advanceTimeBy(milliseconds = 1_000)
|
mainClock.advanceTimeBy(milliseconds = 1_000)
|
||||||
eventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Edit, timelineItem))
|
eventsRecorder.assertSingle(MessagesEvent.HandleAction(TimelineItemAction.Edit, timelineItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a reaction emits the expected Event`() {
|
fun `clicking on a reaction emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(
|
timelineState = aTimelineState(
|
||||||
|
|
@ -414,10 +413,10 @@ class MessagesViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.onAllNodesWithText(
|
onAllNodesWithText(
|
||||||
text = "👍️",
|
text = "👍️",
|
||||||
useUnmergedTree = true,
|
useUnmergedTree = true,
|
||||||
).onFirst().performClick()
|
).onFirst().performClick()
|
||||||
|
|
@ -425,7 +424,7 @@ class MessagesViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `long clicking on a reaction emits the expected Event`() {
|
fun `long clicking on a reaction emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ReactionSummaryEvent>()
|
val eventsRecorder = EventsRecorder<ReactionSummaryEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(
|
timelineState = aTimelineState(
|
||||||
|
|
@ -437,10 +436,10 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.onAllNodesWithText(
|
onAllNodesWithText(
|
||||||
text = "👍️",
|
text = "👍️",
|
||||||
useUnmergedTree = true,
|
useUnmergedTree = true,
|
||||||
).onFirst().performTouchInput { longClick() }
|
).onFirst().performTouchInput { longClick() }
|
||||||
|
|
@ -448,7 +447,7 @@ class MessagesViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on more reaction emits the expected Event`() {
|
fun `clicking on more reaction emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<CustomReactionEvent>()
|
val eventsRecorder = EventsRecorder<CustomReactionEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(
|
timelineState = aTimelineState(
|
||||||
|
|
@ -459,16 +458,16 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
val moreReactionContentDescription = rule.activity.getString(R.string.screen_room_timeline_add_reaction)
|
val moreReactionContentDescription = activity!!.getString(R.string.screen_room_timeline_add_reaction)
|
||||||
rule.onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick()
|
onAllNodesWithContentDescription(moreReactionContentDescription).onFirst().performClick()
|
||||||
eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem))
|
eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on more reaction from action list emits the expected Event`() {
|
fun `clicking on more reaction from action list emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<CustomReactionEvent>()
|
val eventsRecorder = EventsRecorder<CustomReactionEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(
|
timelineState = aTimelineState(
|
||||||
|
|
@ -491,18 +490,18 @@ class MessagesViewTest {
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = stateWithActionListState,
|
state = stateWithActionListState,
|
||||||
)
|
)
|
||||||
val moreReactionContentDescription = rule.activity.getString(CommonStrings.a11y_react_with_other_emojis)
|
val moreReactionContentDescription = activity!!.getString(CommonStrings.a11y_react_with_other_emojis)
|
||||||
rule.onNodeWithContentDescription(moreReactionContentDescription).performClick()
|
onNodeWithContentDescription(moreReactionContentDescription).performClick()
|
||||||
// Give time for the close animation to complete
|
// Give time for the close animation to complete
|
||||||
rule.mainClock.advanceTimeBy(milliseconds = 1_000)
|
mainClock.advanceTimeBy(milliseconds = 1_000)
|
||||||
eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem))
|
eventsRecorder.assertSingle(CustomReactionEvent.ShowCustomReactionSheet(timelineItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on verified user send failure from action list emits the expected Event`() {
|
fun `clicking on verified user send failure from action list emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
val state = aMessagesState()
|
val state = aMessagesState()
|
||||||
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
val timelineItem = state.timelineState.timelineItems.first() as TimelineItem.Event
|
||||||
|
|
@ -519,21 +518,21 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
timelineState = aTimelineState(eventSink = eventsRecorder)
|
timelineState = aTimelineState(eventSink = eventsRecorder)
|
||||||
)
|
)
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = stateWithActionListState,
|
state = stateWithActionListState,
|
||||||
)
|
)
|
||||||
// Clear initial 'LoadMore' event emitted when setting the state
|
// Clear initial 'LoadMore' event emitted when setting the state
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val verifiedUserSendFailure = rule.activity.getString(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, "Alice")
|
val verifiedUserSendFailure = activity!!.getString(CommonStrings.screen_timeline_item_menu_send_failure_changed_identity, "Alice")
|
||||||
rule.onNodeWithText(verifiedUserSendFailure).performClick()
|
onNodeWithText(verifiedUserSendFailure).performClick()
|
||||||
// Give time for the close animation to complete
|
// Give time for the close animation to complete
|
||||||
rule.mainClock.advanceTimeBy(milliseconds = 1_000)
|
mainClock.advanceTimeBy(milliseconds = 1_000)
|
||||||
eventsRecorder.assertSingle(TimelineEvent.ComputeVerifiedUserSendFailure(timelineItem))
|
eventsRecorder.assertSingle(TimelineEvent.ComputeVerifiedUserSendFailure(timelineItem))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a custom emoji emits the expected Events`() {
|
fun `clicking on a custom emoji emits the expected Events`() = runAndroidComposeUiTest {
|
||||||
val aUnicode = "🙈"
|
val aUnicode = "🙈"
|
||||||
val customReactionStateEventsRecorder = EventsRecorder<CustomReactionEvent>()
|
val customReactionStateEventsRecorder = EventsRecorder<CustomReactionEvent>()
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
val eventsRecorder = EventsRecorder<MessagesEvent>()
|
||||||
|
|
@ -563,18 +562,18 @@ class MessagesViewTest {
|
||||||
eventSink = customReactionStateEventsRecorder
|
eventSink = customReactionStateEventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = stateWithCustomReactionState,
|
state = stateWithCustomReactionState,
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(aUnicode, useUnmergedTree = true).performClick()
|
onNodeWithText(aUnicode, useUnmergedTree = true).performClick()
|
||||||
// Give time for the close animation to complete
|
// Give time for the close animation to complete
|
||||||
rule.mainClock.advanceTimeBy(milliseconds = 1_000)
|
mainClock.advanceTimeBy(milliseconds = 1_000)
|
||||||
customReactionStateEventsRecorder.assertSingle(CustomReactionEvent.DismissCustomReactionSheet)
|
customReactionStateEventsRecorder.assertSingle(CustomReactionEvent.DismissCustomReactionSheet)
|
||||||
eventsRecorder.assertSingle(MessagesEvent.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId))
|
eventsRecorder.assertSingle(MessagesEvent.ToggleReaction(aUnicode, timelineItem.eventOrTransactionId))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on pinned messages banner emits the expected Event`() {
|
fun `clicking on pinned messages banner emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
timelineState = aTimelineState(eventSink = eventsRecorder),
|
timelineState = aTimelineState(eventSink = eventsRecorder),
|
||||||
|
|
@ -587,16 +586,16 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.setMessagesView(state = state)
|
setMessagesView(state = state)
|
||||||
// Clear initial 'LoadMore' event emitted when setting the state
|
// Clear initial 'LoadMore' event emitted when setting the state
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.onNodeWithText("This is a pinned message").performClick()
|
onNodeWithText("This is a pinned message").performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
|
eventsRecorder.assertSingle(TimelineEvent.FocusOnEvent(AN_EVENT_ID, debounce = FOCUS_ON_PINNED_EVENT_DEBOUNCE_DURATION_IN_MILLIS.milliseconds))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on successor room button emits expected event`() {
|
fun `clicking on successor room button emits expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
val successorRoomId = RoomId("!successor:server.org")
|
val successorRoomId = RoomId("!successor:server.org")
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
|
|
@ -606,18 +605,18 @@ class MessagesViewTest {
|
||||||
),
|
),
|
||||||
timelineState = aTimelineState(eventSink = eventsRecorder)
|
timelineState = aTimelineState(eventSink = eventsRecorder)
|
||||||
)
|
)
|
||||||
rule.setMessagesView(state = state)
|
setMessagesView(state = state)
|
||||||
// Clear initial 'LoadMore' event emitted when setting the state
|
// Clear initial 'LoadMore' event emitted when setting the state
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val text = rule.activity.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
val text = activity!!.getString(R.string.screen_room_timeline_tombstoned_room_action)
|
||||||
// The bottomsheet subcompose seems to make the node to appear twice
|
// The bottomsheet subcompose seems to make the node to appear twice
|
||||||
rule.onAllNodesWithText(text).onFirst().performClick()
|
onAllNodesWithText(text).onFirst().performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(successorRoomId))
|
eventsRecorder.assertSingle(TimelineEvent.NavigateToPredecessorOrSuccessorRoom(successorRoomId))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on threads list button calls the expected function`() {
|
fun `clicking on threads list button calls the expected function`() = runAndroidComposeUiTest {
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
threads = MessagesState.Threads(
|
threads = MessagesState.Threads(
|
||||||
hasThreads = true,
|
hasThreads = true,
|
||||||
|
|
@ -625,28 +624,28 @@ class MessagesViewTest {
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
val onThreadsListClicked = lambdaRecorder<Unit> {}
|
val onThreadsListClicked = lambdaRecorder<Unit> {}
|
||||||
rule.setMessagesView(
|
setMessagesView(
|
||||||
state = state,
|
state = state,
|
||||||
onThreadsListClicked = onThreadsListClicked,
|
onThreadsListClicked = onThreadsListClicked,
|
||||||
)
|
)
|
||||||
rule.onNodeWithContentDescription("Threads").performClick()
|
onNodeWithContentDescription("Threads").performClick()
|
||||||
onThreadsListClicked.assertions().isCalledOnce()
|
onThreadsListClicked.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `no banner shown when there is no successor room`() {
|
fun `no banner shown when there is no successor room`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<MessagesEvent>(expectEvents = false)
|
||||||
val state = aMessagesState(
|
val state = aMessagesState(
|
||||||
successorRoom = null,
|
successorRoom = null,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
rule.setMessagesView(state = state)
|
setMessagesView(state = state)
|
||||||
rule.assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_message)
|
assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_message)
|
||||||
rule.assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_action)
|
assertNoNodeWithText(R.string.screen_room_timeline_tombstoned_room_action)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMessagesView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setMessagesView(
|
||||||
state: MessagesState,
|
state: MessagesState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onRoomDetailsClick: () -> Unit = EnsureNeverCalled(),
|
onRoomDetailsClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,15 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.crypto.identity
|
package io.element.android.features.messages.impl.crypto.identity
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
import io.element.android.libraries.designsystem.components.avatar.anAvatarData
|
||||||
import io.element.android.libraries.matrix.api.core.UserId
|
import io.element.android.libraries.matrix.api.core.UserId
|
||||||
|
|
@ -21,19 +24,15 @@ import io.element.android.libraries.matrix.ui.room.RoomMemberIdentityStateChange
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class IdentityChangeStateViewTest {
|
class IdentityChangeStateViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `show and resolve pin violation`() {
|
fun `show and resolve pin violation`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<IdentityChangeEvent>()
|
val eventsRecorder = EventsRecorder<IdentityChangeEvent>()
|
||||||
rule.setIdentityChangeStateView(
|
setIdentityChangeStateView(
|
||||||
state = anIdentityChangeState(
|
state = anIdentityChangeState(
|
||||||
listOf(
|
listOf(
|
||||||
RoomMemberIdentityStateChange(
|
RoomMemberIdentityStateChange(
|
||||||
|
|
@ -45,18 +44,18 @@ class IdentityChangeStateViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
rule.onNodeWithText("identity was reset", substring = true).assertExists("should display pin violation warning")
|
onNodeWithText("identity was reset", substring = true).assertExists("should display pin violation warning")
|
||||||
rule.onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid")
|
onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid")
|
||||||
rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname")
|
onNodeWithText("Alice", substring = true).assertExists("should display user displayname")
|
||||||
|
|
||||||
rule.clickOn(res = CommonStrings.action_dismiss)
|
clickOn(res = CommonStrings.action_dismiss)
|
||||||
eventsRecorder.assertSingle(IdentityChangeEvent.PinIdentity(UserId("@alice:localhost")))
|
eventsRecorder.assertSingle(IdentityChangeEvent.PinIdentity(UserId("@alice:localhost")))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `show and resolve verification violation`() {
|
fun `show and resolve verification violation`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<IdentityChangeEvent>()
|
val eventsRecorder = EventsRecorder<IdentityChangeEvent>()
|
||||||
rule.setIdentityChangeStateView(
|
setIdentityChangeStateView(
|
||||||
state = anIdentityChangeState(
|
state = anIdentityChangeState(
|
||||||
listOf(
|
listOf(
|
||||||
RoomMemberIdentityStateChange(
|
RoomMemberIdentityStateChange(
|
||||||
|
|
@ -68,17 +67,17 @@ class IdentityChangeStateViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
rule.onNodeWithText("identity was reset", substring = true).assertExists("should display verification violation warning")
|
onNodeWithText("identity was reset", substring = true).assertExists("should display verification violation warning")
|
||||||
rule.onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid")
|
onNodeWithText("@alice:localhost", substring = true).assertExists("should display user mxid")
|
||||||
rule.onNodeWithText("Alice", substring = true).assertExists("should display user displayname")
|
onNodeWithText("Alice", substring = true).assertExists("should display user displayname")
|
||||||
|
|
||||||
rule.clickOn(res = CommonStrings.crypto_identity_change_withdraw_verification_action)
|
clickOn(res = CommonStrings.crypto_identity_change_withdraw_verification_action)
|
||||||
eventsRecorder.assertSingle(IdentityChangeEvent.WithdrawVerification(UserId("@alice:localhost")))
|
eventsRecorder.assertSingle(IdentityChangeEvent.WithdrawVerification(UserId("@alice:localhost")))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `Should not show any banner if no violations`() {
|
fun `Should not show any banner if no violations`() = runAndroidComposeUiTest {
|
||||||
rule.setIdentityChangeStateView(
|
setIdentityChangeStateView(
|
||||||
state = anIdentityChangeState(
|
state = anIdentityChangeState(
|
||||||
listOf(
|
listOf(
|
||||||
RoomMemberIdentityStateChange(
|
RoomMemberIdentityStateChange(
|
||||||
|
|
@ -93,10 +92,10 @@ class IdentityChangeStateViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
rule.onNodeWithText("identity was reset", substring = true).assertDoesNotExist()
|
onNodeWithText("identity was reset", substring = true).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setIdentityChangeStateView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setIdentityChangeStateView(
|
||||||
state: IdentityChangeState,
|
state: IdentityChangeState,
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
|
|
|
||||||
|
|
@ -6,54 +6,53 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.crypto.sendfailure.resolve
|
package io.element.android.features.messages.impl.crypto.sendfailure.resolve
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.setSafeContent
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ResolveVerifiedUserSendFailureViewTest {
|
class ResolveVerifiedUserSendFailureViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on resolve and resend emit the expected event`() {
|
fun `clicking on resolve and resend emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ResolveVerifiedUserSendFailureEvent>()
|
val eventsRecorder = EventsRecorder<ResolveVerifiedUserSendFailureEvent>()
|
||||||
rule.setResolveVerifiedUserSendFailureView(
|
setResolveVerifiedUserSendFailureView(
|
||||||
state = aResolveVerifiedUserSendFailureState(
|
state = aResolveVerifiedUserSendFailureState(
|
||||||
verifiedUserSendFailure = aChangedIdentitySendFailure(),
|
verifiedUserSendFailure = aChangedIdentitySendFailure(),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
rule.clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title)
|
clickOn(res = CommonStrings.screen_resolve_send_failure_changed_identity_primary_button_title)
|
||||||
eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
|
eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.ResolveAndResend)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on retry emit the expected event`() {
|
fun `clicking on retry emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ResolveVerifiedUserSendFailureEvent>()
|
val eventsRecorder = EventsRecorder<ResolveVerifiedUserSendFailureEvent>()
|
||||||
rule.setResolveVerifiedUserSendFailureView(
|
setResolveVerifiedUserSendFailureView(
|
||||||
state = aResolveVerifiedUserSendFailureState(
|
state = aResolveVerifiedUserSendFailureState(
|
||||||
verifiedUserSendFailure = aChangedIdentitySendFailure(),
|
verifiedUserSendFailure = aChangedIdentitySendFailure(),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
rule.clickOn(res = CommonStrings.action_retry)
|
clickOn(res = CommonStrings.action_retry)
|
||||||
eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.Retry)
|
eventsRecorder.assertSingle(ResolveVerifiedUserSendFailureEvent.Retry)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setResolveVerifiedUserSendFailureView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setResolveVerifiedUserSendFailureView(
|
||||||
state: ResolveVerifiedUserSendFailureState,
|
state: ResolveVerifiedUserSendFailureState,
|
||||||
) {
|
) {
|
||||||
setSafeContent {
|
setSafeContent {
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.link
|
package io.element.android.features.messages.impl.link
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -19,51 +22,46 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.wysiwyg.link.Link
|
import io.element.android.wysiwyg.link.Link
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class LinkViewTest {
|
class LinkViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on cancel emits the expected event`() {
|
fun `clicking on cancel emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LinkEvent>()
|
val eventsRecorder = EventsRecorder<LinkEvent>()
|
||||||
rule.setLinkView(
|
setLinkView(
|
||||||
aLinkState(
|
aLinkState(
|
||||||
linkClick = ConfirmingLinkClick(aLink),
|
linkClick = ConfirmingLinkClick(aLink),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
LinkEvent.Cancel
|
LinkEvent.Cancel
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on continue emits the expected event`() {
|
fun `clicking on continue emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LinkEvent>()
|
val eventsRecorder = EventsRecorder<LinkEvent>()
|
||||||
rule.setLinkView(
|
setLinkView(
|
||||||
aLinkState(
|
aLinkState(
|
||||||
linkClick = ConfirmingLinkClick(aLink),
|
linkClick = ConfirmingLinkClick(aLink),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
LinkEvent.Confirm
|
LinkEvent.Confirm
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `success state invokes the callback and emits the expected event`() {
|
fun `success state invokes the callback and emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<LinkEvent>()
|
val eventsRecorder = EventsRecorder<LinkEvent>()
|
||||||
ensureCalledOnceWithParam(aLink) { callback ->
|
ensureCalledOnceWithParam(aLink) { callback ->
|
||||||
rule.setLinkView(
|
setLinkView(
|
||||||
aLinkState(
|
aLinkState(
|
||||||
linkClick = AsyncAction.Success(aLink),
|
linkClick = AsyncAction.Success(aLink),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -77,7 +75,7 @@ class LinkViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setLinkView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setLinkView(
|
||||||
state: LinkState,
|
state: LinkState,
|
||||||
onLinkValid: (Link) -> Unit = EnsureNeverCalledWithParam(),
|
onLinkValid: (Link) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.pinned.banner
|
package io.element.android.features.messages.impl.pinned.banner
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onRoot
|
import androidx.compose.ui.test.onRoot
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.matrix.api.core.EventId
|
import io.element.android.libraries.matrix.api.core.EventId
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
|
|
@ -22,49 +25,45 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class PinnedMessagesBannerViewTest {
|
class PinnedMessagesBannerViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on the banner invoke expected callback`() {
|
fun `clicking on the banner invoke expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PinnedMessagesBannerEvent>()
|
val eventsRecorder = EventsRecorder<PinnedMessagesBannerEvent>()
|
||||||
val state = aLoadedPinnedMessagesBannerState(
|
val state = aLoadedPinnedMessagesBannerState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
val pinnedEventId = state.currentPinnedMessage.eventId
|
val pinnedEventId = state.currentPinnedMessage.eventId
|
||||||
ensureCalledOnceWithParam(pinnedEventId) { callback ->
|
ensureCalledOnceWithParam(pinnedEventId) { callback ->
|
||||||
rule.setPinnedMessagesBannerView(
|
setPinnedMessagesBannerView(
|
||||||
state = state,
|
state = state,
|
||||||
onClick = callback
|
onClick = callback
|
||||||
)
|
)
|
||||||
rule.onRoot().performClick()
|
onRoot().performClick()
|
||||||
eventsRecorder.assertSingle(PinnedMessagesBannerEvent.MoveToNextPinned)
|
eventsRecorder.assertSingle(PinnedMessagesBannerEvent.MoveToNextPinned)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on view all emit the expected event`() {
|
fun `clicking on view all emit the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PinnedMessagesBannerEvent>(expectEvents = true)
|
val eventsRecorder = EventsRecorder<PinnedMessagesBannerEvent>(expectEvents = true)
|
||||||
val state = aLoadedPinnedMessagesBannerState(
|
val state = aLoadedPinnedMessagesBannerState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setPinnedMessagesBannerView(
|
setPinnedMessagesBannerView(
|
||||||
state = state,
|
state = state,
|
||||||
onViewAllClick = callback
|
onViewAllClick = callback
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.screen_room_pinned_banner_view_all_button_title)
|
clickOn(CommonStrings.screen_room_pinned_banner_view_all_button_title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPinnedMessagesBannerView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setPinnedMessagesBannerView(
|
||||||
state: PinnedMessagesBannerState,
|
state: PinnedMessagesBannerState,
|
||||||
onClick: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
onClick: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onViewAllClick: () -> Unit = EnsureNeverCalled(),
|
onViewAllClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,16 +6,19 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.pinned.list
|
package io.element.android.features.messages.impl.pinned.list
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.longClick
|
import androidx.compose.ui.test.longClick
|
||||||
import androidx.compose.ui.test.onAllNodesWithText
|
import androidx.compose.ui.test.onAllNodesWithText
|
||||||
import androidx.compose.ui.test.onFirst
|
import androidx.compose.ui.test.onFirst
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTouchInput
|
import androidx.compose.ui.test.performTouchInput
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.messages.impl.actionlist.ActionListEvent
|
import io.element.android.features.messages.impl.actionlist.ActionListEvent
|
||||||
import io.element.android.features.messages.impl.actionlist.anActionListState
|
import io.element.android.features.messages.impl.actionlist.anActionListState
|
||||||
|
|
@ -31,33 +34,28 @@ import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import io.element.android.tests.testutils.setSafeContent
|
import io.element.android.tests.testutils.setSafeContent
|
||||||
import io.element.android.wysiwyg.link.Link
|
import io.element.android.wysiwyg.link.Link
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class PinnedMessagesListViewTest {
|
class PinnedMessagesListViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back calls the expected callback`() {
|
fun `clicking on back calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PinnedMessagesListEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PinnedMessagesListEvent>(expectEvents = false)
|
||||||
val state = aLoadedPinnedMessagesListState(
|
val state = aLoadedPinnedMessagesListState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setPinnedMessagesListView(
|
setPinnedMessagesListView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = callback
|
onBackClick = callback
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on an event calls the expected callback`() {
|
fun `click on an event calls the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PinnedMessagesListEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PinnedMessagesListEvent>(expectEvents = false)
|
||||||
val content = aTimelineItemFileContent()
|
val content = aTimelineItemFileContent()
|
||||||
val state = aLoadedPinnedMessagesListState(
|
val state = aLoadedPinnedMessagesListState(
|
||||||
|
|
@ -67,16 +65,16 @@ class PinnedMessagesListViewTest {
|
||||||
|
|
||||||
val event = state.timelineItems.first() as TimelineItem.Event
|
val event = state.timelineItems.first() as TimelineItem.Event
|
||||||
ensureCalledOnceWithParam(event) { callback ->
|
ensureCalledOnceWithParam(event) { callback ->
|
||||||
rule.setPinnedMessagesListView(
|
setPinnedMessagesListView(
|
||||||
state = state,
|
state = state,
|
||||||
onEventClick = callback
|
onEventClick = callback
|
||||||
)
|
)
|
||||||
rule.onAllNodesWithText(content.filename).onFirst().performClick()
|
onAllNodesWithText(content.filename).onFirst().performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `long click on an event emits the expected event`() {
|
fun `long click on an event emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<ActionListEvent>(expectEvents = true)
|
val eventsRecorder = EventsRecorder<ActionListEvent>(expectEvents = true)
|
||||||
val content = aTimelineItemFileContent()
|
val content = aTimelineItemFileContent()
|
||||||
val state = aLoadedPinnedMessagesListState(
|
val state = aLoadedPinnedMessagesListState(
|
||||||
|
|
@ -84,10 +82,10 @@ class PinnedMessagesListViewTest {
|
||||||
actionListState = anActionListState(eventSink = eventsRecorder)
|
actionListState = anActionListState(eventSink = eventsRecorder)
|
||||||
)
|
)
|
||||||
|
|
||||||
rule.setPinnedMessagesListView(
|
setPinnedMessagesListView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.onAllNodesWithText(content.filename).onFirst()
|
onAllNodesWithText(content.filename).onFirst()
|
||||||
.performTouchInput {
|
.performTouchInput {
|
||||||
longClick()
|
longClick()
|
||||||
}
|
}
|
||||||
|
|
@ -96,7 +94,7 @@ class PinnedMessagesListViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPinnedMessagesListView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setPinnedMessagesListView(
|
||||||
state: PinnedMessagesListState,
|
state: PinnedMessagesListState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onEventClick: (event: TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
|
onEventClick: (event: TimelineItem.Event) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline
|
package io.element.android.features.messages.impl.timeline
|
||||||
|
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.platform.LocalInspectionMode
|
import androidx.compose.ui.platform.LocalInspectionMode
|
||||||
import androidx.compose.ui.test.junit4.createComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runComposeUiTest
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter
|
import io.element.android.features.messages.impl.utils.FakeMentionSpanFormatter
|
||||||
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
import io.element.android.libraries.core.extensions.runCatchingExceptions
|
||||||
|
|
@ -18,15 +21,12 @@ import io.element.android.libraries.matrix.test.A_USER_ID
|
||||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme
|
import io.element.android.libraries.textcomposer.mentions.MentionSpanTheme
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.RobolectricTestRunner
|
import org.robolectric.RobolectricTestRunner
|
||||||
|
|
||||||
@RunWith(RobolectricTestRunner::class)
|
@RunWith(RobolectricTestRunner::class)
|
||||||
class DefaultHtmlConverterProviderTest {
|
class DefaultHtmlConverterProviderTest {
|
||||||
@get:Rule val composeTestRule = createComposeRule()
|
|
||||||
|
|
||||||
private val provider = DefaultHtmlConverterProvider(
|
private val provider = DefaultHtmlConverterProvider(
|
||||||
mentionSpanProvider = MentionSpanProvider(
|
mentionSpanProvider = MentionSpanProvider(
|
||||||
permalinkParser = FakePermalinkParser(),
|
permalinkParser = FakePermalinkParser(),
|
||||||
|
|
@ -43,8 +43,8 @@ class DefaultHtmlConverterProviderTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `calling provide after calling Update first should return an HtmlConverter`() {
|
fun `calling provide after calling Update first should return an HtmlConverter`() = runComposeUiTest {
|
||||||
composeTestRule.setContent {
|
setContent {
|
||||||
CompositionLocalProvider(LocalInspectionMode provides true) {
|
CompositionLocalProvider(LocalInspectionMode provides true) {
|
||||||
provider.Update()
|
provider.Update()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,15 +6,18 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline
|
package io.element.android.features.messages.impl.timeline
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithTag
|
import androidx.compose.ui.test.onNodeWithTag
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performScrollToIndex
|
import androidx.compose.ui.test.performScrollToIndex
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.messages.impl.timeline.components.MessageShieldData
|
import io.element.android.features.messages.impl.timeline.components.MessageShieldData
|
||||||
import io.element.android.features.messages.impl.timeline.components.aCriticalShield
|
import io.element.android.features.messages.impl.timeline.components.aCriticalShield
|
||||||
|
|
@ -39,19 +42,15 @@ import io.element.android.wysiwyg.link.Link
|
||||||
import kotlinx.collections.immutable.persistentListOf
|
import kotlinx.collections.immutable.persistentListOf
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import org.junit.Ignore
|
import org.junit.Ignore
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class TimelineViewTest {
|
class TimelineViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reaching the end of the timeline with more events to load emits a LoadMore event`() {
|
fun `reaching the end of the timeline with more events to load emits a LoadMore event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf<TimelineItem>(
|
timelineItems = persistentListOf<TimelineItem>(
|
||||||
TimelineItem.Virtual(
|
TimelineItem.Virtual(
|
||||||
|
|
@ -66,9 +65,9 @@ class TimelineViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `reaching the end of the timeline does not send a LoadMore event`() {
|
fun `reaching the end of the timeline does not send a LoadMore event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -78,9 +77,9 @@ class TimelineViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `scroll to bottom on live timeline does not emit the Event`() {
|
fun `scroll to bottom on live timeline does not emit the Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
||||||
isLive = true,
|
isLive = true,
|
||||||
|
|
@ -92,14 +91,14 @@ class TimelineViewTest {
|
||||||
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
|
val contentDescription = activity!!.getString(CommonStrings.a11y_jump_to_bottom)
|
||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
onNodeWithContentDescription(contentDescription).performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `scroll to bottom on detached timeline emits the expected Event`() {
|
fun `scroll to bottom on detached timeline emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
||||||
isLive = false,
|
isLive = false,
|
||||||
|
|
@ -110,15 +109,15 @@ class TimelineViewTest {
|
||||||
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
val contentDescription = rule.activity.getString(CommonStrings.a11y_jump_to_bottom)
|
val contentDescription = activity!!.getString(CommonStrings.a11y_jump_to_bottom)
|
||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
onNodeWithContentDescription(contentDescription).performClick()
|
||||||
eventsRecorder.assertSingle(TimelineEvent.JumpToLive)
|
eventsRecorder.assertSingle(TimelineEvent.JumpToLive)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `an empty timeline triggers a prefetch`() {
|
fun `an empty timeline triggers a prefetch`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf(),
|
timelineItems = persistentListOf(),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -129,9 +128,9 @@ class TimelineViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `show shield dialog`() {
|
fun `show shield dialog`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf<TimelineItem>(
|
timelineItems = persistentListOf<TimelineItem>(
|
||||||
aTimelineItemEvent(
|
aTimelineItemEvent(
|
||||||
|
|
@ -143,8 +142,8 @@ class TimelineViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val contentDescription = rule.activity.getString(CommonStrings.a11y_encryption_details)
|
val contentDescription = activity!!.getString(CommonStrings.a11y_encryption_details)
|
||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
onNodeWithContentDescription(contentDescription).performClick()
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
TimelineEvent.OnScrollFinished(0),
|
TimelineEvent.OnScrollFinished(0),
|
||||||
|
|
@ -154,9 +153,9 @@ class TimelineViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `hide shield dialog`() {
|
fun `hide shield dialog`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
rule.setTimelineView(
|
setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
timelineItems = persistentListOf(aTimelineItemEvent(content = aTimelineItemImageContent())),
|
||||||
isLive = false,
|
isLive = false,
|
||||||
|
|
@ -167,16 +166,16 @@ class TimelineViewTest {
|
||||||
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
eventsRecorder.assertSingle(TimelineEvent.OnScrollFinished(firstIndex = 0))
|
||||||
eventsRecorder.clear()
|
eventsRecorder.clear()
|
||||||
|
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog)
|
eventsRecorder.assertSingle(TimelineEvent.HideShieldDialog)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Ignore(
|
@Ignore(
|
||||||
"performScrollToIndex in compose tests no longer sets LazyListState.isScrollInProgress to true, so the LoadMore event is not emitted." +
|
"performScrollToIndex in compose tests no longer sets LazyListState.isScrollInProgress to true, so the LoadMore event is not emitted." +
|
||||||
"This needs to be reworked to use a different approach to check the LoadMore event was emitted."
|
"This needs to be reworked to use a different approach to check the LoadMore event was emitted."
|
||||||
)
|
)
|
||||||
@Test
|
@Test
|
||||||
fun `scrolling near to the start of the loaded items triggers a pre-fetch`() {
|
fun `scrolling near to the start of the loaded items triggers a pre-fetch`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent>()
|
||||||
val items = List<TimelineItem>(200) {
|
val items = List<TimelineItem>(200) {
|
||||||
aTimelineItemEvent(
|
aTimelineItemEvent(
|
||||||
|
|
@ -185,7 +184,7 @@ class TimelineViewTest {
|
||||||
)
|
)
|
||||||
}.toImmutableList()
|
}.toImmutableList()
|
||||||
|
|
||||||
rule.setTimelineView(
|
setTimelineView(
|
||||||
state = aTimelineState(
|
state = aTimelineState(
|
||||||
timelineItems = items,
|
timelineItems = items,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -194,9 +193,9 @@ class TimelineViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
rule.onNodeWithTag("timeline").performScrollToIndex(180)
|
onNodeWithTag("timeline").performScrollToIndex(180)
|
||||||
|
|
||||||
rule.mainClock.advanceTimeBy(1000)
|
mainClock.advanceTimeBy(1000)
|
||||||
|
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -207,7 +206,7 @@ class TimelineViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTimelineView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setTimelineView(
|
||||||
state: TimelineState,
|
state: TimelineState,
|
||||||
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
|
timelineProtectionState: TimelineProtectionState = aTimelineProtectionState(),
|
||||||
onUserDataClick: (MatrixUser) -> Unit = EnsureNeverCalledWithParam(),
|
onUserDataClick: (MatrixUser) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
|
|
|
||||||
|
|
@ -6,12 +6,15 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.event
|
package io.element.android.features.messages.impl.timeline.components.event
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.hasText
|
import androidx.compose.ui.test.hasText
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
import io.element.android.features.messages.impl.timeline.TimelineEvent
|
||||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||||
|
|
@ -20,14 +23,11 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.pressTag
|
import io.element.android.tests.testutils.pressTag
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class TimelineItemPollViewTest {
|
class TimelineItemPollViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `answering a poll with first answer should emit a PollAnswerSelected event`() {
|
fun `answering a poll with first answer should emit a PollAnswerSelected event`() {
|
||||||
testAnswer(answerIndex = 0)
|
testAnswer(answerIndex = 0)
|
||||||
|
|
@ -38,17 +38,17 @@ class TimelineItemPollViewTest {
|
||||||
testAnswer(answerIndex = 1)
|
testAnswer(answerIndex = 1)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testAnswer(answerIndex: Int) {
|
private fun testAnswer(answerIndex: Int) = runAndroidComposeUiTest<ComponentActivity> {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
||||||
val content = aTimelineItemPollContent()
|
val content = aTimelineItemPollContent()
|
||||||
rule.setContent {
|
setContent {
|
||||||
TimelineItemPollView(
|
TimelineItemPollView(
|
||||||
content = content,
|
content = content,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val answer = content.answerItems[answerIndex].answer
|
val answer = content.answerItems[answerIndex].answer
|
||||||
rule.onNode(
|
onNode(
|
||||||
matcher = hasText(answer.text),
|
matcher = hasText(answer.text),
|
||||||
useUnmergedTree = true,
|
useUnmergedTree = true,
|
||||||
).performClick()
|
).performClick()
|
||||||
|
|
@ -56,38 +56,38 @@ class TimelineItemPollViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `editing a poll should emit a PollEditClicked event`() {
|
fun `editing a poll should emit a PollEditClicked event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
||||||
val content = aTimelineItemPollContent(
|
val content = aTimelineItemPollContent(
|
||||||
isMine = true,
|
isMine = true,
|
||||||
isEditable = true,
|
isEditable = true,
|
||||||
)
|
)
|
||||||
rule.setContent {
|
setContent {
|
||||||
TimelineItemPollView(
|
TimelineItemPollView(
|
||||||
content = content,
|
content = content,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.clickOn(CommonStrings.action_edit_poll)
|
clickOn(CommonStrings.action_edit_poll)
|
||||||
eventsRecorder.assertSingle(TimelineEvent.EditPoll(content.eventId!!))
|
eventsRecorder.assertSingle(TimelineEvent.EditPoll(content.eventId!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `closing a poll should emit a PollEndClicked event`() {
|
fun `closing a poll should emit a PollEndClicked event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
val eventsRecorder = EventsRecorder<TimelineEvent.TimelineItemPollEvent>()
|
||||||
val content = aTimelineItemPollContent(
|
val content = aTimelineItemPollContent(
|
||||||
isMine = true,
|
isMine = true,
|
||||||
)
|
)
|
||||||
rule.setContent {
|
setContent {
|
||||||
TimelineItemPollView(
|
TimelineItemPollView(
|
||||||
content = content,
|
content = content,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
rule.clickOn(CommonStrings.action_end_poll)
|
clickOn(CommonStrings.action_end_poll)
|
||||||
// A confirmation dialog should be shown
|
// A confirmation dialog should be shown
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
rule.pressTag(TestTags.dialogPositive.value)
|
pressTag(TestTags.dialogPositive.value)
|
||||||
eventsRecorder.assertSingle(TimelineEvent.EndPoll(content.eventId!!))
|
eventsRecorder.assertSingle(TimelineEvent.EndPoll(content.eventId!!))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,17 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.components.event
|
package io.element.android.features.messages.impl.timeline.components.event
|
||||||
|
|
||||||
import android.text.SpannableString
|
import android.text.SpannableString
|
||||||
import android.text.SpannedString
|
import android.text.SpannedString
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.core.text.buildSpannedString
|
import androidx.core.text.buildSpannedString
|
||||||
import androidx.core.text.inSpans
|
import androidx.core.text.inSpans
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
|
|
@ -38,45 +41,40 @@ import io.element.android.tests.testutils.lambda.assert
|
||||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||||
import io.element.android.wysiwyg.view.spans.CustomMentionSpan
|
import io.element.android.wysiwyg.view.spans.CustomMentionSpan
|
||||||
import kotlinx.coroutines.CompletableDeferred
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
import kotlinx.coroutines.test.runTest
|
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class TimelineTextViewTest {
|
class TimelineTextViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
private val mentionSpanTheme = MentionSpanTheme(currentUserId = A_USER_ID)
|
private val mentionSpanTheme = MentionSpanTheme(currentUserId = A_USER_ID)
|
||||||
private val formatLambda = lambdaRecorder<MentionType, CharSequence> { mentionType -> mentionType.toString() }
|
private val formatLambda = lambdaRecorder<MentionType, CharSequence> { mentionType -> mentionType.toString() }
|
||||||
private val mentionSpanFormatter = FakeMentionSpanFormatter(formatLambda)
|
private val mentionSpanFormatter = FakeMentionSpanFormatter(formatLambda)
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getTextWithResolvedMentions - does nothing for a non spannable CharSequence`() = runTest {
|
fun `getTextWithResolvedMentions - does nothing for a non spannable CharSequence`() = runAndroidComposeUiTest {
|
||||||
val charSequence = "Hello <a href=\"https://matrix.to/#/@alice:example.com\">@alice:example.com</a>"
|
val charSequence = "Hello <a href=\"https://matrix.to/#/@alice:example.com\">@alice:example.com</a>"
|
||||||
val mentionSpanUpdater = aMentionSpanUpdater()
|
val mentionSpanUpdater = aMentionSpanUpdater()
|
||||||
val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
||||||
|
|
||||||
assertThat(result.getMentionSpans()).isEmpty()
|
assertThat(result.getMentionSpans()).isEmpty()
|
||||||
assert(formatLambda).isNeverCalled()
|
assert(formatLambda).isNeverCalled()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getTextWithResolvedMentions - does nothing if there are no mentions`() = runTest {
|
fun `getTextWithResolvedMentions - does nothing if there are no mentions`() = runAndroidComposeUiTest {
|
||||||
val charSequence = SpannableString("Hello <a href=\"https://matrix.to/#/@alice:example.com\">@alice:example.com</a>")
|
val charSequence = SpannableString("Hello <a href=\"https://matrix.to/#/@alice:example.com\">@alice:example.com</a>")
|
||||||
val mentionSpanUpdater = aMentionSpanUpdater()
|
val mentionSpanUpdater = aMentionSpanUpdater()
|
||||||
val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
||||||
|
|
||||||
assertThat(result.getMentionSpans()).isEmpty()
|
assertThat(result.getMentionSpans()).isEmpty()
|
||||||
assert(formatLambda).isNeverCalled()
|
assert(formatLambda).isNeverCalled()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getTextWithResolvedMentions - just returns the body if there is no formattedBody`() = runTest {
|
fun `getTextWithResolvedMentions - just returns the body if there is no formattedBody`() = runAndroidComposeUiTest {
|
||||||
val charSequence = "Hello <a href=\"https://matrix.to/#/@alice:example.com\">@alice:example.com</a>"
|
val charSequence = "Hello <a href=\"https://matrix.to/#/@alice:example.com\">@alice:example.com</a>"
|
||||||
val mentionSpanUpdater = aMentionSpanUpdater()
|
val mentionSpanUpdater = aMentionSpanUpdater()
|
||||||
val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(body = charSequence, formattedBody = null))
|
val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(body = charSequence, formattedBody = null))
|
||||||
|
|
||||||
assertThat(result.getMentionSpans()).isEmpty()
|
assertThat(result.getMentionSpans()).isEmpty()
|
||||||
assertThat(result.toString()).isEqualTo(charSequence)
|
assertThat(result.toString()).isEqualTo(charSequence)
|
||||||
|
|
@ -84,7 +82,7 @@ class TimelineTextViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getTextWithResolvedMentions - with Room mention format correctly`() = runTest {
|
fun `getTextWithResolvedMentions - with Room mention format correctly`() = runAndroidComposeUiTest {
|
||||||
val mentionType = MentionType.Room(roomIdOrAlias = A_ROOM_ID_2.toRoomIdOrAlias())
|
val mentionType = MentionType.Room(roomIdOrAlias = A_ROOM_ID_2.toRoomIdOrAlias())
|
||||||
val charSequence = buildSpannedString {
|
val charSequence = buildSpannedString {
|
||||||
append("Hello ")
|
append("Hello ")
|
||||||
|
|
@ -93,7 +91,7 @@ class TimelineTextViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val mentionSpanUpdater = aMentionSpanUpdater()
|
val mentionSpanUpdater = aMentionSpanUpdater()
|
||||||
val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
||||||
|
|
||||||
val expectedDisplayText = mentionType.toString()
|
val expectedDisplayText = mentionType.toString()
|
||||||
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
|
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
|
||||||
|
|
@ -102,7 +100,7 @@ class TimelineTextViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getTextWithResolvedMentions - replaces MentionSpan's text`() = runTest {
|
fun `getTextWithResolvedMentions - replaces MentionSpan's text`() = runAndroidComposeUiTest {
|
||||||
val mentionType = MentionType.User(userId = A_USER_ID)
|
val mentionType = MentionType.User(userId = A_USER_ID)
|
||||||
val charSequence = buildSpannedString {
|
val charSequence = buildSpannedString {
|
||||||
append("Hello ")
|
append("Hello ")
|
||||||
|
|
@ -111,7 +109,7 @@ class TimelineTextViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
val mentionSpanUpdater = aMentionSpanUpdater()
|
val mentionSpanUpdater = aMentionSpanUpdater()
|
||||||
val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
||||||
|
|
||||||
val expectedDisplayText = mentionType.toString()
|
val expectedDisplayText = mentionType.toString()
|
||||||
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
|
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
|
||||||
|
|
@ -119,7 +117,7 @@ class TimelineTextViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `getTextWithResolvedMentions - replaces MentionSpan's text inside CustomMentionSpan`() = runTest {
|
fun `getTextWithResolvedMentions - replaces MentionSpan's text inside CustomMentionSpan`() = runAndroidComposeUiTest {
|
||||||
val mentionType = MentionType.User(userId = A_USER_ID)
|
val mentionType = MentionType.User(userId = A_USER_ID)
|
||||||
val charSequence = buildSpannedString {
|
val charSequence = buildSpannedString {
|
||||||
append("Hello ")
|
append("Hello ")
|
||||||
|
|
@ -129,12 +127,12 @@ class TimelineTextViewTest {
|
||||||
}
|
}
|
||||||
val mentionSpanUpdater = aMentionSpanUpdater()
|
val mentionSpanUpdater = aMentionSpanUpdater()
|
||||||
val expectedDisplayText = mentionType.toString()
|
val expectedDisplayText = mentionType.toString()
|
||||||
val result = rule.getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
val result = getText(mentionSpanUpdater, aTextContentWithFormattedBody(charSequence))
|
||||||
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
|
assertThat(result.getMentionSpans().firstOrNull()?.displayText.toString()).isEqualTo(expectedDisplayText)
|
||||||
assert(formatLambda).isCalledOnce()
|
assert(formatLambda).isCalledOnce()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.getText(
|
private suspend fun AndroidComposeUiTest<ComponentActivity>.getText(
|
||||||
mentionSpanUpdater: MentionSpanUpdater,
|
mentionSpanUpdater: MentionSpanUpdater,
|
||||||
content: TimelineItemTextBasedContent,
|
content: TimelineItemTextBasedContent,
|
||||||
): CharSequence {
|
): CharSequence {
|
||||||
|
|
|
||||||
|
|
@ -6,56 +6,55 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.messages.impl.timeline.protection
|
package io.element.android.features.messages.impl.timeline.protection
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.designsystem.theme.components.Text
|
import io.element.android.libraries.designsystem.theme.components.Text
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class ProtectedViewTest {
|
class ProtectedViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when hideContent is false, the content is rendered`() {
|
fun `when hideContent is false, the content is rendered`() = runAndroidComposeUiTest {
|
||||||
rule.setProtectedView(
|
setProtectedView(
|
||||||
hideContent = false,
|
hideContent = false,
|
||||||
content = {
|
content = {
|
||||||
Text("Hello")
|
Text("Hello")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("Hello").assertExists()
|
onNodeWithText("Hello").assertExists()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when hideContent is true, the content is not rendered, and user can reveal it`() {
|
fun `when hideContent is true, the content is not rendered, and user can reveal it`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setProtectedView(
|
setProtectedView(
|
||||||
hideContent = true,
|
hideContent = true,
|
||||||
onShowClick = it,
|
onShowClick = it,
|
||||||
content = {
|
content = {
|
||||||
Text("Hello")
|
Text("Hello")
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("Hello").assertDoesNotExist()
|
onNodeWithText("Hello").assertDoesNotExist()
|
||||||
rule.clickOn(CommonStrings.action_show)
|
clickOn(CommonStrings.action_show)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setProtectedView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setProtectedView(
|
||||||
hideContent: Boolean = false,
|
hideContent: Boolean = false,
|
||||||
onShowClick: () -> Unit = { lambdaError() },
|
onShowClick: () -> Unit = { lambdaError() },
|
||||||
content: @Composable () -> Unit = {},
|
content: @Composable () -> Unit = {},
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api(projects.features.messages.impl)
|
api(projects.features.messages.impl)
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.test)
|
implementation(projects.libraries.matrix.test)
|
||||||
implementation(projects.libraries.audio.test)
|
implementation(projects.libraries.audio.test)
|
||||||
implementation(projects.libraries.mediaplayer.test)
|
implementation(projects.libraries.mediaplayer.test)
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.poll.impl.history
|
package io.element.android.features.poll.impl.history
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.poll.api.pollcontent.aPollContentState
|
import io.element.android.features.poll.api.pollcontent.aPollContentState
|
||||||
import io.element.android.features.poll.impl.R
|
import io.element.android.features.poll.impl.R
|
||||||
|
|
@ -26,34 +29,29 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class PollHistoryViewTest {
|
class PollHistoryViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes the expected callback`() {
|
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PollHistoryEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PollHistoryEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setPollHistoryViewView(
|
setPollHistoryViewView(
|
||||||
aPollHistoryState(
|
aPollHistoryState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
goBack = it
|
goBack = it
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on edit poll invokes the expected callback`() {
|
fun `clicking on edit poll invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PollHistoryEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PollHistoryEvents>(expectEvents = false)
|
||||||
val eventId = EventId("\$anEventId")
|
val eventId = EventId("\$anEventId")
|
||||||
val state = aPollHistoryState(
|
val state = aPollHistoryState(
|
||||||
|
|
@ -69,17 +67,17 @@ class PollHistoryViewTest {
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
ensureCalledOnceWithParam(eventId) {
|
ensureCalledOnceWithParam(eventId) {
|
||||||
rule.setPollHistoryViewView(
|
setPollHistoryViewView(
|
||||||
state = state,
|
state = state,
|
||||||
onEditPoll = it
|
onEditPoll = it
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_edit_poll)
|
clickOn(CommonStrings.action_edit_poll)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on poll end emits the expected Event`() {
|
fun `clicking on poll end emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PollHistoryEvents>()
|
val eventsRecorder = EventsRecorder<PollHistoryEvents>()
|
||||||
val eventId = EventId("\$anEventId")
|
val eventId = EventId("\$anEventId")
|
||||||
val state = aPollHistoryState(
|
val state = aPollHistoryState(
|
||||||
|
|
@ -95,16 +93,16 @@ class PollHistoryViewTest {
|
||||||
),
|
),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
rule.setPollHistoryViewView(
|
setPollHistoryViewView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_end_poll)
|
clickOn(CommonStrings.action_end_poll)
|
||||||
// Cancel the dialog
|
// Cancel the dialog
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
// Do it again, and confirm the dialog
|
// Do it again, and confirm the dialog
|
||||||
rule.clickOn(CommonStrings.action_end_poll)
|
clickOn(CommonStrings.action_end_poll)
|
||||||
eventsRecorder.assertEmpty()
|
eventsRecorder.assertEmpty()
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
PollHistoryEvents.EndPoll(eventId)
|
PollHistoryEvents.EndPoll(eventId)
|
||||||
)
|
)
|
||||||
|
|
@ -112,7 +110,7 @@ class PollHistoryViewTest {
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on poll answer emits the expected Event`() {
|
fun `clicking on poll answer emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PollHistoryEvents>()
|
val eventsRecorder = EventsRecorder<PollHistoryEvents>()
|
||||||
val eventId = EventId("\$anEventId")
|
val eventId = EventId("\$anEventId")
|
||||||
val state = aPollHistoryState(
|
val state = aPollHistoryState(
|
||||||
|
|
@ -129,10 +127,10 @@ class PollHistoryViewTest {
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
)
|
)
|
||||||
val answer = state.pollHistoryItems.ongoing.first().state.answerItems.first().answer
|
val answer = state.pollHistoryItems.ongoing.first().state.answerItems.first().answer
|
||||||
rule.setPollHistoryViewView(
|
setPollHistoryViewView(
|
||||||
state = state,
|
state = state,
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(
|
onNodeWithText(
|
||||||
text = answer.text,
|
text = answer.text,
|
||||||
useUnmergedTree = true,
|
useUnmergedTree = true,
|
||||||
).performClick()
|
).performClick()
|
||||||
|
|
@ -142,14 +140,14 @@ class PollHistoryViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on past tab emits the expected Event`() {
|
fun `clicking on past tab emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PollHistoryEvents>()
|
val eventsRecorder = EventsRecorder<PollHistoryEvents>()
|
||||||
rule.setPollHistoryViewView(
|
setPollHistoryViewView(
|
||||||
aPollHistoryState(
|
aPollHistoryState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_polls_history_filter_past)
|
clickOn(R.string.screen_polls_history_filter_past)
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
PollHistoryEvents.SelectFilter(filter = PollHistoryFilter.PAST)
|
PollHistoryEvents.SelectFilter(filter = PollHistoryFilter.PAST)
|
||||||
)
|
)
|
||||||
|
|
@ -157,22 +155,22 @@ class PollHistoryViewTest {
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on load more emits the expected Event`() {
|
fun `clicking on load more emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PollHistoryEvents>()
|
val eventsRecorder = EventsRecorder<PollHistoryEvents>()
|
||||||
rule.setPollHistoryViewView(
|
setPollHistoryViewView(
|
||||||
aPollHistoryState(
|
aPollHistoryState(
|
||||||
hasMoreToLoad = true,
|
hasMoreToLoad = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_load_more)
|
clickOn(CommonStrings.action_load_more)
|
||||||
eventsRecorder.assertSingle(
|
eventsRecorder.assertSingle(
|
||||||
PollHistoryEvents.LoadMore
|
PollHistoryEvents.LoadMore
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPollHistoryViewView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setPollHistoryViewView(
|
||||||
state: PollHistoryState,
|
state: PollHistoryState,
|
||||||
onEditPoll: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
onEditPoll: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
goBack: () -> Unit = EnsureNeverCalled(),
|
goBack: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ android {
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
implementation(projects.libraries.architecture)
|
||||||
implementation(projects.libraries.matrix.api)
|
implementation(projects.libraries.matrix.api)
|
||||||
api(projects.features.poll.api)
|
api(projects.features.poll.api)
|
||||||
implementation(libs.kotlinx.collections.immutable)
|
implementation(libs.kotlinx.collections.immutable)
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ dependencies {
|
||||||
implementation(projects.libraries.permissions.api)
|
implementation(projects.libraries.permissions.api)
|
||||||
implementation(projects.libraries.push.api)
|
implementation(projects.libraries.push.api)
|
||||||
implementation(projects.libraries.pushproviders.api)
|
implementation(projects.libraries.pushproviders.api)
|
||||||
|
implementation(projects.libraries.sessionStorage.api)
|
||||||
implementation(projects.libraries.uiUtils)
|
implementation(projects.libraries.uiUtils)
|
||||||
implementation(projects.libraries.fullscreenintent.api)
|
implementation(projects.libraries.fullscreenintent.api)
|
||||||
implementation(projects.features.rageshake.api)
|
implementation(projects.features.rageshake.api)
|
||||||
|
|
|
||||||
|
|
@ -6,11 +6,14 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.about
|
package io.element.android.features.preferences.impl.about
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
|
@ -19,51 +22,47 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class AboutViewTest {
|
class AboutViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes back callback`() {
|
fun `clicking on back invokes back callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setAboutView(
|
setAboutView(
|
||||||
anAboutState(),
|
anAboutState(),
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on an item invokes the expected callback`() {
|
fun `clicking on an item invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val state = anAboutState()
|
val state = anAboutState()
|
||||||
ensureCalledOnceWithParam(state.elementLegals.first()) { callback ->
|
ensureCalledOnceWithParam(state.elementLegals.first()) { callback ->
|
||||||
rule.setAboutView(
|
setAboutView(
|
||||||
state,
|
state,
|
||||||
onElementLegalClick = callback,
|
onElementLegalClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(state.elementLegals.first().titleRes)
|
clickOn(state.elementLegals.first().titleRes)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on the open source licenses invokes the expected callback`() {
|
fun `clicking on the open source licenses invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setAboutView(
|
setAboutView(
|
||||||
anAboutState(),
|
anAboutState(),
|
||||||
onOpenSourceLicensesClick = callback,
|
onOpenSourceLicensesClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_open_source_licenses)
|
clickOn(CommonStrings.common_open_source_licenses)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAboutView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setAboutView(
|
||||||
state: AboutState,
|
state: AboutState,
|
||||||
onElementLegalClick: (ElementLegal) -> Unit = EnsureNeverCalledWithParam(),
|
onElementLegalClick: (ElementLegal) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onOpenSourceLicensesClick: () -> Unit = EnsureNeverCalled(),
|
onOpenSourceLicensesClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.advanced
|
package io.element.android.features.preferences.impl.advanced
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import com.google.common.truth.Truth.assertThat
|
import com.google.common.truth.Truth.assertThat
|
||||||
import im.vector.app.features.analytics.plan.Interaction
|
import im.vector.app.features.analytics.plan.Interaction
|
||||||
|
|
@ -30,104 +33,99 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import kotlinx.collections.immutable.toImmutableList
|
import kotlinx.collections.immutable.toImmutableList
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class AdvancedSettingsViewTest {
|
class AdvancedSettingsViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes the expected callback`() {
|
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = it
|
onBackClick = it
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on other theme emits the expected event`() {
|
fun `clicking on other theme emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_appearance)
|
clickOn(CommonStrings.common_appearance)
|
||||||
rule.clickOn(CommonStrings.common_dark)
|
clickOn(CommonStrings.common_dark)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTheme(ThemeOption.Dark))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `black theme is shown when available`() {
|
fun `black theme is shown when available`() = runAndroidComposeUiTest {
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
availableThemeOptions = ThemeOption.entries.toImmutableList(),
|
availableThemeOptions = ThemeOption.entries.toImmutableList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_appearance)
|
clickOn(CommonStrings.common_appearance)
|
||||||
rule.run {
|
run {
|
||||||
val text = activity.getString(CommonStrings.common_black)
|
val text = activity!!.getString(CommonStrings.common_black)
|
||||||
onNodeWithText(text).assertExists()
|
onNodeWithText(text).assertExists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `black theme is hidden when unavailable`() {
|
fun `black theme is hidden when unavailable`() = runAndroidComposeUiTest {
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
availableThemeOptions = ThemeOption.entries.filterNot { it == ThemeOption.Black }.toImmutableList(),
|
availableThemeOptions = ThemeOption.entries.filterNot { it == ThemeOption.Black }.toImmutableList(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_appearance)
|
clickOn(CommonStrings.common_appearance)
|
||||||
rule.assertNoNodeWithText(CommonStrings.common_black)
|
assertNoNodeWithText(CommonStrings.common_black)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on View source emits the expected event`() {
|
fun `clicking on View source emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_view_source)
|
clickOn(CommonStrings.action_view_source)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetDeveloperModeEnabled(true))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetDeveloperModeEnabled(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Share presence emits the expected event`() {
|
fun `clicking on Share presence emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_share_presence)
|
clickOn(R.string.screen_advanced_settings_share_presence)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetSharePresenceEnabled(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on media to enable compression emits the expected event`() {
|
fun `clicking on media to enable compression emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
analyticsService = analyticsService
|
analyticsService = analyticsService
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_media_compression_description)
|
clickOn(R.string.screen_advanced_settings_media_compression_description)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(true))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(true))
|
||||||
assertThat(analyticsService.capturedEvents).isEqualTo(
|
assertThat(analyticsService.capturedEvents).isEqualTo(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -139,17 +137,17 @@ class AdvancedSettingsViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on media to disable compression emits the expected event`() {
|
fun `clicking on media to disable compression emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
val analyticsService = FakeAnalyticsService()
|
val analyticsService = FakeAnalyticsService()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
mediaOptimizationState = MediaOptimizationState.AllMedia(isEnabled = true),
|
mediaOptimizationState = MediaOptimizationState.AllMedia(isEnabled = true),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
analyticsService = analyticsService
|
analyticsService = analyticsService
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_media_compression_description)
|
clickOn(R.string.screen_advanced_settings_media_compression_description)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(false))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetCompressMedia(false))
|
||||||
assertThat(analyticsService.capturedEvents).isEqualTo(
|
assertThat(analyticsService.capturedEvents).isEqualTo(
|
||||||
listOf(
|
listOf(
|
||||||
|
|
@ -162,65 +160,65 @@ class AdvancedSettingsViewTest {
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1080dp")
|
@Config(qualifiers = "h1080dp")
|
||||||
fun `clicking on hide invite avatars emits the expected event`() {
|
fun `clicking on hide invite avatars emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
hideInviteAvatars = false
|
hideInviteAvatars = false
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title)
|
clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetHideInviteAvatars(true))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetHideInviteAvatars(true))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1080dp")
|
@Config(qualifiers = "h1080dp")
|
||||||
fun `clicking on timeline media preview always hide emits the expected event`() {
|
fun `clicking on timeline media preview always hide emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
timelineMediaPreviewValue = MediaPreviewValue.On
|
timelineMediaPreviewValue = MediaPreviewValue.On
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide)
|
clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Off))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1080dp")
|
@Config(qualifiers = "h1080dp")
|
||||||
fun `clicking on timeline media preview private rooms emits the expected event`() {
|
fun `clicking on timeline media preview private rooms emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
timelineMediaPreviewValue = MediaPreviewValue.On
|
timelineMediaPreviewValue = MediaPreviewValue.On
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms)
|
clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.Private))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1080dp")
|
@Config(qualifiers = "h1080dp")
|
||||||
fun `clicking on timeline media preview always show emits the expected event`() {
|
fun `clicking on timeline media preview always show emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>()
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
timelineMediaPreviewValue = MediaPreviewValue.Off
|
timelineMediaPreviewValue = MediaPreviewValue.Off
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_show)
|
clickOn(R.string.screen_advanced_settings_show_media_timeline_always_show)
|
||||||
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.On))
|
eventsRecorder.assertSingle(AdvancedSettingsEvents.SetTimelineMediaPreviewValue(MediaPreviewValue.On))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1080dp")
|
@Config(qualifiers = "h1080dp")
|
||||||
fun `hide invite avatars toggle is disabled when action is loading`() {
|
fun `hide invite avatars toggle is disabled when action is loading`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>(expectEvents = false)
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
hideInviteAvatars = false,
|
hideInviteAvatars = false,
|
||||||
|
|
@ -228,14 +226,14 @@ class AdvancedSettingsViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// The toggle should be disabled, so clicking should not emit any events
|
// The toggle should be disabled, so clicking should not emit any events
|
||||||
rule.clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title)
|
clickOn(R.string.screen_advanced_settings_hide_invite_avatars_toggle_title)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(qualifiers = "h1080dp")
|
@Config(qualifiers = "h1080dp")
|
||||||
fun `timeline media preview options are disabled when action is loading`() {
|
fun `timeline media preview options are disabled when action is loading`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<AdvancedSettingsEvents>(expectEvents = false)
|
||||||
rule.setAdvancedSettingsView(
|
setAdvancedSettingsView(
|
||||||
state = aAdvancedSettingsState(
|
state = aAdvancedSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
timelineMediaPreviewValue = MediaPreviewValue.On,
|
timelineMediaPreviewValue = MediaPreviewValue.On,
|
||||||
|
|
@ -243,12 +241,12 @@ class AdvancedSettingsViewTest {
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
// The options should be disabled, so clicking should not emit any events
|
// The options should be disabled, so clicking should not emit any events
|
||||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide)
|
clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms)
|
clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAdvancedSettingsView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setAdvancedSettingsView(
|
||||||
state: AdvancedSettingsState,
|
state: AdvancedSettingsState,
|
||||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.blockedusers
|
package io.element.android.features.preferences.impl.blockedusers
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -23,72 +26,67 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class BlockedUserViewTest {
|
class BlockedUserViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes back callback`() {
|
fun `clicking on back invokes back callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<BlockedUsersEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<BlockedUsersEvents>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setBlockedUsersView(
|
setBlockedUsersView(
|
||||||
aBlockedUsersState(
|
aBlockedUsersState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a user emits the expected Event`() {
|
fun `clicking on a user emits the expected Event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<BlockedUsersEvents>()
|
val eventsRecorder = EventsRecorder<BlockedUsersEvents>()
|
||||||
val userList = aMatrixUserList()
|
val userList = aMatrixUserList()
|
||||||
rule.setBlockedUsersView(
|
setBlockedUsersView(
|
||||||
aBlockedUsersState(
|
aBlockedUsersState(
|
||||||
blockedUsers = userList,
|
blockedUsers = userList,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(userList.first().displayName.orEmpty()).performClick()
|
onNodeWithText(userList.first().displayName.orEmpty()).performClick()
|
||||||
eventsRecorder.assertSingle(BlockedUsersEvents.Unblock(userList.first().userId))
|
eventsRecorder.assertSingle(BlockedUsersEvents.Unblock(userList.first().userId))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on cancel sends a BlockedUsersEvents`() {
|
fun `clicking on cancel sends a BlockedUsersEvents`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<BlockedUsersEvents>()
|
val eventsRecorder = EventsRecorder<BlockedUsersEvents>()
|
||||||
rule.setBlockedUsersView(
|
setBlockedUsersView(
|
||||||
aBlockedUsersState(
|
aBlockedUsersState(
|
||||||
unblockUserAction = AsyncAction.ConfirmingNoParams,
|
unblockUserAction = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
clickOn(CommonStrings.action_cancel)
|
||||||
eventsRecorder.assertSingle(BlockedUsersEvents.Cancel)
|
eventsRecorder.assertSingle(BlockedUsersEvents.Cancel)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on confirm sends a BlockedUsersEvents`() {
|
fun `clicking on confirm sends a BlockedUsersEvents`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<BlockedUsersEvents>()
|
val eventsRecorder = EventsRecorder<BlockedUsersEvents>()
|
||||||
rule.setBlockedUsersView(
|
setBlockedUsersView(
|
||||||
aBlockedUsersState(
|
aBlockedUsersState(
|
||||||
unblockUserAction = AsyncAction.ConfirmingNoParams,
|
unblockUserAction = AsyncAction.ConfirmingNoParams,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_blocked_users_unblock_alert_action)
|
clickOn(R.string.screen_blocked_users_unblock_alert_action)
|
||||||
eventsRecorder.assertSingle(BlockedUsersEvents.ConfirmUnblock)
|
eventsRecorder.assertSingle(BlockedUsersEvents.ConfirmUnblock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setBlockedUsersView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setBlockedUsersView(
|
||||||
state: BlockedUsersState,
|
state: BlockedUsersState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.developer
|
package io.element.android.features.preferences.impl.developer
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||||
|
|
@ -20,76 +23,71 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class DeveloperSettingsViewTest {
|
class DeveloperSettingsViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes the expected callback`() {
|
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setDeveloperSettingsView(
|
setDeveloperSettingsView(
|
||||||
state = aDeveloperSettingsState(
|
state = aDeveloperSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = it
|
onBackClick = it
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h2000dp")
|
@Config(qualifiers = "h2000dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on push history notification invokes the expected callback`() {
|
fun `clicking on push history notification invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setDeveloperSettingsView(
|
setDeveloperSettingsView(
|
||||||
state = aDeveloperSettingsState(
|
state = aDeveloperSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onPushHistoryClick = it
|
onPushHistoryClick = it
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.troubleshoot_notifications_entry_point_push_history_title)
|
clickOn(R.string.troubleshoot_notifications_entry_point_push_history_title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h2000dp")
|
@Config(qualifiers = "h2000dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on open showkase invokes the expected callback`() {
|
fun `clicking on open showkase invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setDeveloperSettingsView(
|
setDeveloperSettingsView(
|
||||||
state = aDeveloperSettingsState(
|
state = aDeveloperSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onOpenShowkase = it
|
onOpenShowkase = it
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("Open Showkase browser").performClick()
|
onNodeWithText("Open Showkase browser").performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h2200dp")
|
@Config(qualifiers = "h2200dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on clear cache emits the expected event`() {
|
fun `clicking on clear cache emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>()
|
val eventsRecorder = EventsRecorder<DeveloperSettingsEvents>()
|
||||||
rule.setDeveloperSettingsView(
|
setDeveloperSettingsView(
|
||||||
state = aDeveloperSettingsState(
|
state = aDeveloperSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("Clear cache").performClick()
|
onNodeWithText("Clear cache").performClick()
|
||||||
eventsRecorder.assertSingle(DeveloperSettingsEvents.ClearCache)
|
eventsRecorder.assertSingle(DeveloperSettingsEvents.ClearCache)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setDeveloperSettingsView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setDeveloperSettingsView(
|
||||||
state: DeveloperSettingsState,
|
state: DeveloperSettingsState,
|
||||||
onOpenShowkase: () -> Unit = EnsureNeverCalled(),
|
onOpenShowkase: () -> Unit = EnsureNeverCalled(),
|
||||||
onPushHistoryClick: () -> Unit = EnsureNeverCalled(),
|
onPushHistoryClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -5,19 +5,22 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.developer.appsettings
|
package io.element.android.features.preferences.impl.developer.appsettings
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.filterToOne
|
import androidx.compose.ui.test.filterToOne
|
||||||
import androidx.compose.ui.test.hasAnyAncestor
|
import androidx.compose.ui.test.hasAnyAncestor
|
||||||
import androidx.compose.ui.test.isDialog
|
import androidx.compose.ui.test.isDialog
|
||||||
import androidx.compose.ui.test.isEditable
|
import androidx.compose.ui.test.isEditable
|
||||||
import androidx.compose.ui.test.isFocusable
|
import androidx.compose.ui.test.isFocusable
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTextInput
|
import androidx.compose.ui.test.performTextInput
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
import io.element.android.features.preferences.impl.developer.tracing.LogLevelItem
|
||||||
|
|
@ -27,78 +30,73 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class AppDeveloperSettingsPageTest {
|
class AppDeveloperSettingsPageTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes the expected callback`() {
|
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AppDeveloperSettingsEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<AppDeveloperSettingsEvent>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setAppDeveloperSettingsView(
|
setAppDeveloperSettingsView(
|
||||||
state = anAppDeveloperSettingsState(
|
state = anAppDeveloperSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = it
|
onBackClick = it
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1500dp")
|
@Config(qualifiers = "h1500dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on element call url open the dialogs and submit emits the expected event`() {
|
fun `clicking on element call url open the dialogs and submit emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AppDeveloperSettingsEvent>()
|
val eventsRecorder = EventsRecorder<AppDeveloperSettingsEvent>()
|
||||||
rule.setAppDeveloperSettingsView(
|
setAppDeveloperSettingsView(
|
||||||
state = anAppDeveloperSettingsState(
|
state = anAppDeveloperSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_element_call_base_url)
|
clickOn(R.string.screen_advanced_settings_element_call_base_url)
|
||||||
val textInputNode = rule.onAllNodes(isEditable().and(isFocusable())).filterToOne(hasAnyAncestor(isDialog()))
|
val textInputNode = onAllNodes(isEditable().and(isFocusable())).filterToOne(hasAnyAncestor(isDialog()))
|
||||||
textInputNode.performTextInput("https://call.element.dev")
|
textInputNode.performTextInput("https://call.element.dev")
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertSingle(AppDeveloperSettingsEvent.SetCustomElementCallBaseUrl("https://call.element.dev"))
|
eventsRecorder.assertSingle(AppDeveloperSettingsEvent.SetCustomElementCallBaseUrl("https://call.element.dev"))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h2000dp")
|
@Config(qualifiers = "h2000dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on open showkase invokes the expected callback`() {
|
fun `clicking on open showkase invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AppDeveloperSettingsEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<AppDeveloperSettingsEvent>(expectEvents = false)
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setAppDeveloperSettingsView(
|
setAppDeveloperSettingsView(
|
||||||
state = anAppDeveloperSettingsState(
|
state = anAppDeveloperSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onOpenShowkase = it
|
onOpenShowkase = it
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("Open Showkase browser").performClick()
|
onNodeWithText("Open Showkase browser").performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on log level emits the expected event`() {
|
fun `clicking on log level emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<AppDeveloperSettingsEvent>()
|
val eventsRecorder = EventsRecorder<AppDeveloperSettingsEvent>()
|
||||||
rule.setAppDeveloperSettingsView(
|
setAppDeveloperSettingsView(
|
||||||
state = anAppDeveloperSettingsState(
|
state = anAppDeveloperSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("Tracing log level").performClick()
|
onNodeWithText("Tracing log level").performClick()
|
||||||
rule.onNodeWithText("Debug").performClick()
|
onNodeWithText("Debug").performClick()
|
||||||
eventsRecorder.assertSingle(AppDeveloperSettingsEvent.SetTracingLogLevel(LogLevelItem.DEBUG))
|
eventsRecorder.assertSingle(AppDeveloperSettingsEvent.SetTracingLogLevel(LogLevelItem.DEBUG))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setAppDeveloperSettingsView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setAppDeveloperSettingsView(
|
||||||
state: AppDeveloperSettingsState,
|
state: AppDeveloperSettingsState,
|
||||||
onOpenShowkase: () -> Unit = EnsureNeverCalled(),
|
onOpenShowkase: () -> Unit = EnsureNeverCalled(),
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,13 +6,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.notifications
|
package io.element.android.features.preferences.impl.notifications
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
|
|
@ -25,76 +28,71 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
import org.robolectric.annotation.Config
|
import org.robolectric.annotation.Config
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class NotificationSettingsViewTest {
|
class NotificationSettingsViewTest {
|
||||||
@get:Rule
|
|
||||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes the expected callback`() {
|
fun `clicking on back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = it
|
onBackClick = it
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on troubleshoot notification invokes the expected callback`() {
|
fun `clicking on troubleshoot notification invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
ensureCalledOnce {
|
ensureCalledOnce {
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onTroubleshootNotificationsClick = it
|
onTroubleshootNotificationsClick = it
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.troubleshoot_notifications_entry_point_title)
|
clickOn(R.string.troubleshoot_notifications_entry_point_title)
|
||||||
}
|
}
|
||||||
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on group chats invokes the expected callback`() {
|
fun `clicking on group chats invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
ensureCalledOnceWithParam(false) {
|
ensureCalledOnceWithParam(false) {
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onOpenEditDefault = it
|
onOpenEditDefault = it
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_notification_settings_group_chats)
|
clickOn(R.string.screen_notification_settings_group_chats)
|
||||||
}
|
}
|
||||||
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on direct chats invokes the expected callback`() {
|
fun `clicking on direct chats invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
ensureCalledOnceWithParam(true) {
|
ensureCalledOnceWithParam(true) {
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onOpenEditDefault = it
|
onOpenEditDefault = it
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_notification_settings_direct_chats)
|
clickOn(R.string.screen_notification_settings_direct_chats)
|
||||||
}
|
}
|
||||||
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
eventsRecorder.assertSingle(NotificationSettingsEvents.RefreshSystemNotificationsEnabled)
|
||||||
}
|
}
|
||||||
|
|
@ -111,15 +109,15 @@ class NotificationSettingsViewTest {
|
||||||
testNotificationToggle(false)
|
testNotificationToggle(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testNotificationToggle(initialState: Boolean) {
|
private fun testNotificationToggle(initialState: Boolean) = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
appNotificationEnabled = initialState,
|
appNotificationEnabled = initialState,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_notification_settings_enable_notifications)
|
clickOn(R.string.screen_notification_settings_enable_notifications)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
||||||
|
|
@ -140,15 +138,15 @@ class NotificationSettingsViewTest {
|
||||||
testAtRoomToggle(false)
|
testAtRoomToggle(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testAtRoomToggle(initialState: Boolean) {
|
private fun testAtRoomToggle(initialState: Boolean) = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
atRoomNotificationsEnabled = initialState,
|
atRoomNotificationsEnabled = initialState,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_notification_settings_room_mention_label)
|
clickOn(R.string.screen_notification_settings_room_mention_label)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
||||||
|
|
@ -169,15 +167,15 @@ class NotificationSettingsViewTest {
|
||||||
testInvitationToggle(false)
|
testInvitationToggle(false)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun testInvitationToggle(initialState: Boolean) {
|
private fun testInvitationToggle(initialState: Boolean) = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
inviteForMeNotificationsEnabled = initialState,
|
inviteForMeNotificationsEnabled = initialState,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_notification_settings_invite_for_me_label)
|
clickOn(R.string.screen_notification_settings_invite_for_me_label)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
||||||
|
|
@ -188,15 +186,15 @@ class NotificationSettingsViewTest {
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `with an error configuration, clicking on continue emits the expected events`() {
|
fun `with an error configuration, clicking on continue emits the expected events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
changeNotificationSettingAction = AsyncAction.Failure(AN_EXCEPTION),
|
changeNotificationSettingAction = AsyncAction.Failure(AN_EXCEPTION),
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
||||||
|
|
@ -207,15 +205,15 @@ class NotificationSettingsViewTest {
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `with invalid configuration, clicking on continue emits the expected events`() {
|
fun `with invalid configuration, clicking on continue emits the expected events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aInvalidNotificationSettingsState(
|
state = aInvalidNotificationSettingsState(
|
||||||
fixFailed = false,
|
fixFailed = false,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_continue)
|
clickOn(CommonStrings.action_continue)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
||||||
|
|
@ -226,15 +224,15 @@ class NotificationSettingsViewTest {
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `with invalid configuration and error, clicking on OK emits the expected events`() {
|
fun `with invalid configuration and error, clicking on OK emits the expected events`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aInvalidNotificationSettingsState(
|
state = aInvalidNotificationSettingsState(
|
||||||
fixFailed = true,
|
fixFailed = true,
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
clickOn(CommonStrings.action_ok)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
||||||
|
|
@ -245,14 +243,14 @@ class NotificationSettingsViewTest {
|
||||||
|
|
||||||
@Config(qualifiers = "h1024dp")
|
@Config(qualifiers = "h1024dp")
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on Push notification provider emits the expected event`() {
|
fun `clicking on Push notification provider emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_advanced_settings_push_provider_android)
|
clickOn(R.string.screen_advanced_settings_push_provider_android)
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
||||||
|
|
@ -262,16 +260,16 @@ class NotificationSettingsViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on a push provider emits the expected event`() {
|
fun `clicking on a push provider emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
val eventsRecorder = EventsRecorder<NotificationSettingsEvents>()
|
||||||
rule.setNotificationSettingsView(
|
setNotificationSettingsView(
|
||||||
state = aValidNotificationSettingsState(
|
state = aValidNotificationSettingsState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
showChangePushProviderDialog = true,
|
showChangePushProviderDialog = true,
|
||||||
availablePushDistributors = listOf(aDistributor("P1"), aDistributor("P2"))
|
availablePushDistributors = listOf(aDistributor("P1"), aDistributor("P2"))
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("P2").performClick()
|
onNodeWithText("P2").performClick()
|
||||||
eventsRecorder.assertList(
|
eventsRecorder.assertList(
|
||||||
listOf(
|
listOf(
|
||||||
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
NotificationSettingsEvents.RefreshSystemNotificationsEnabled,
|
||||||
|
|
@ -281,7 +279,7 @@ class NotificationSettingsViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setNotificationSettingsView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setNotificationSettingsView(
|
||||||
state: NotificationSettingsState,
|
state: NotificationSettingsState,
|
||||||
onOpenEditDefault: (isOneToOne: Boolean) -> Unit = EnsureNeverCalledWithParam(),
|
onOpenEditDefault: (isOneToOne: Boolean) -> Unit = EnsureNeverCalledWithParam(),
|
||||||
onTroubleshootNotificationsClick: () -> Unit = EnsureNeverCalled(),
|
onTroubleshootNotificationsClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -5,13 +5,16 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.root
|
package io.element.android.features.preferences.impl.root
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.features.preferences.impl.R
|
import io.element.android.features.preferences.impl.R
|
||||||
import io.element.android.libraries.matrix.api.user.MatrixUser
|
import io.element.android.libraries.matrix.api.user.MatrixUser
|
||||||
|
|
@ -25,49 +28,45 @@ import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class PreferencesRootViewTest {
|
class PreferencesRootViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back invokes back callback`() {
|
fun `clicking on back invokes back callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
eventSink = eventsRecorder
|
eventSink = eventsRecorder
|
||||||
),
|
),
|
||||||
onBackClick = callback,
|
onBackClick = callback,
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on User profile invokes the expected callback`() {
|
fun `click on User profile invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
val user = aMatrixUser()
|
val user = aMatrixUser()
|
||||||
ensureCalledOnceWithParam(user) { callback ->
|
ensureCalledOnceWithParam(user) { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
myUser = user,
|
myUser = user,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenUserProfile = callback,
|
onOpenUserProfile = callback,
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("Alice").performClick()
|
onNodeWithText("Alice").performClick()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on other session sends a SwitchToSession`() {
|
fun `clicking on other session sends a SwitchToSession`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>()
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>()
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
isMultiAccountEnabled = true,
|
isMultiAccountEnabled = true,
|
||||||
otherSessions = listOf(
|
otherSessions = listOf(
|
||||||
|
|
@ -79,366 +78,366 @@ class PreferencesRootViewTest {
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText("Bob").performClick()
|
onNodeWithText("Bob").performClick()
|
||||||
eventsRecorder.assertSingle(PreferencesRootEvent.SwitchToSession(A_USER_ID_2))
|
eventsRecorder.assertSingle(PreferencesRootEvent.SwitchToSession(A_USER_ID_2))
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Add account invokes the expected callback`() {
|
fun `click on Add account invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
isMultiAccountEnabled = true,
|
isMultiAccountEnabled = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onAddAccountClick = callback,
|
onAddAccountClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_add_another_account)
|
clickOn(CommonStrings.common_add_another_account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when multi account is not enabled, item is not shown`() {
|
fun `when multi account is not enabled, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
isMultiAccountEnabled = false,
|
isMultiAccountEnabled = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.common_add_another_account)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.common_add_another_account)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Encryption invokes the expected callback`() {
|
fun `click on Encryption invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showSecureBackup = true,
|
showSecureBackup = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onSecureBackupClick = callback,
|
onSecureBackupClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_encryption)
|
clickOn(CommonStrings.common_encryption)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when showSecureBackup is false, item is not shown`() {
|
fun `when showSecureBackup is false, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showSecureBackup = false,
|
showSecureBackup = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.common_encryption)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.common_encryption)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Manage account invokes the expected callback`() {
|
fun `click on Manage account invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnceWithParam("aUrl") { callback ->
|
ensureCalledOnceWithParam("aUrl") { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
accountManagementUrl = "aUrl",
|
accountManagementUrl = "aUrl",
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onManageAccountClick = callback,
|
onManageAccountClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_manage_account_and_devices)
|
clickOn(CommonStrings.action_manage_account_and_devices)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when accountManagementUrl is null, item is not shown`() {
|
fun `when accountManagementUrl is null, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
accountManagementUrl = null,
|
accountManagementUrl = null,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.action_manage_account_and_devices)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.action_manage_account_and_devices)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Link new devices invokes the expected callback`() {
|
fun `click on Link new devices invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showLinkNewDevice = true,
|
showLinkNewDevice = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onLinkNewDeviceClick = callback,
|
onLinkNewDeviceClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_link_new_device)
|
clickOn(CommonStrings.common_link_new_device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when showLinkNewDevice is false, item is not shown`() {
|
fun `when showLinkNewDevice is false, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showLinkNewDevice = false,
|
showLinkNewDevice = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.common_link_new_device)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.common_link_new_device)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Analytics invokes the expected callback`() {
|
fun `click on Analytics invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showAnalyticsSettings = true,
|
showAnalyticsSettings = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenAnalytics = callback,
|
onOpenAnalytics = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_analytics)
|
clickOn(CommonStrings.common_analytics)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when showAnalyticsSettings is false, item is not shown`() {
|
fun `when showAnalyticsSettings is false, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showAnalyticsSettings = false,
|
showAnalyticsSettings = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.common_analytics)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.common_analytics)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Report a problem invokes the expected callback`() {
|
fun `click on Report a problem invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
canReportBug = true,
|
canReportBug = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenRageShake = callback,
|
onOpenRageShake = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_report_a_problem)
|
clickOn(CommonStrings.common_report_a_problem)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when canReportBug is false, item is not shown`() {
|
fun `when canReportBug is false, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
canReportBug = false,
|
canReportBug = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.common_report_a_problem)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.common_report_a_problem)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Screen lock invokes the expected callback`() {
|
fun `click on Screen lock invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenLockScreenSettings = callback,
|
onOpenLockScreenSettings = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_screen_lock)
|
clickOn(CommonStrings.common_screen_lock)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on About invokes the expected callback`() {
|
fun `click on About invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenAbout = callback,
|
onOpenAbout = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_about)
|
clickOn(CommonStrings.common_about)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Developer settings invokes the expected callback`() {
|
fun `click on Developer settings invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showDeveloperSettings = true,
|
showDeveloperSettings = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenDeveloperSettings = callback,
|
onOpenDeveloperSettings = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_developer_options)
|
clickOn(CommonStrings.common_developer_options)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when showDeveloperSettings is false, item is not shown`() {
|
fun `when showDeveloperSettings is false, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showDeveloperSettings = false,
|
showDeveloperSettings = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.common_developer_options)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.common_developer_options)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Advanced settings invokes the expected callback`() {
|
fun `click on Advanced settings invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenAdvancedSettings = callback,
|
onOpenAdvancedSettings = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_advanced_settings)
|
clickOn(CommonStrings.common_advanced_settings)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Labs invokes the expected callback`() {
|
fun `click on Labs invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showLabsItem = true,
|
showLabsItem = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenLabs = callback,
|
onOpenLabs = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_labs_title)
|
clickOn(R.string.screen_labs_title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when showLabsItem is false, item is not shown`() {
|
fun `when showLabsItem is false, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
showLabsItem = false,
|
showLabsItem = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(R.string.screen_labs_title)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(R.string.screen_labs_title)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Notification invokes the expected callback`() {
|
fun `click on Notification invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenNotificationSettings = callback,
|
onOpenNotificationSettings = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(R.string.screen_notification_settings_title)
|
clickOn(R.string.screen_notification_settings_title)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Blocked users invokes the expected callback`() {
|
fun `click on Blocked users invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
nbOfBlockedUsers = 1,
|
nbOfBlockedUsers = 1,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onOpenBlockedUsers = callback,
|
onOpenBlockedUsers = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.common_blocked_users)
|
clickOn(CommonStrings.common_blocked_users)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when nbOfBlockedUsers is 0, item is not shown`() {
|
fun `when nbOfBlockedUsers is 0, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
nbOfBlockedUsers = 0,
|
nbOfBlockedUsers = 0,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.common_blocked_users)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.common_blocked_users)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Remove this device invokes the expected callback`() {
|
fun `click on Remove this device invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onSignOutClick = callback,
|
onSignOutClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_signout)
|
clickOn(CommonStrings.action_signout)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `click on Deactivate invokes the expected callback`() {
|
fun `click on Deactivate invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
canDeactivateAccount = true,
|
canDeactivateAccount = true,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
onDeactivateClick = callback,
|
onDeactivateClick = callback,
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_delete_account)
|
clickOn(CommonStrings.action_delete_account)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `when canDeactivateAccount is false, item is not shown`() {
|
fun `when canDeactivateAccount is false, item is not shown`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>(expectEvents = false)
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
canDeactivateAccount = false,
|
canDeactivateAccount = false,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.action_delete_account)).assertDoesNotExist()
|
onNodeWithText(activity!!.getString(CommonStrings.action_delete_account)).assertDoesNotExist()
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on version sends a PreferencesRootEvents`() {
|
fun `clicking on version sends a PreferencesRootEvents`() = runAndroidComposeUiTest {
|
||||||
val version = "VERSION"
|
val version = "VERSION"
|
||||||
val eventsRecorder = EventsRecorder<PreferencesRootEvent>()
|
val eventsRecorder = EventsRecorder<PreferencesRootEvent>()
|
||||||
rule.setView(
|
setView(
|
||||||
aPreferencesRootState(
|
aPreferencesRootState(
|
||||||
version = version,
|
version = version,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.onNodeWithText(version).performClick()
|
onNodeWithText(version).performClick()
|
||||||
eventsRecorder.assertSingle(PreferencesRootEvent.OnVersionInfoClick)
|
eventsRecorder.assertSingle(PreferencesRootEvent.OnVersionInfoClick)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setView(
|
||||||
state: PreferencesRootState,
|
state: PreferencesRootState,
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||||
onAddAccountClick: () -> Unit = EnsureNeverCalled(),
|
onAddAccountClick: () -> Unit = EnsureNeverCalled(),
|
||||||
|
|
|
||||||
|
|
@ -6,14 +6,17 @@
|
||||||
* Please see LICENSE files in the repository root for full details.
|
* Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@file:OptIn(ExperimentalTestApi::class)
|
||||||
|
|
||||||
package io.element.android.features.preferences.impl.user.editprofile
|
package io.element.android.features.preferences.impl.user.editprofile
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
import androidx.compose.ui.test.ExperimentalTestApi
|
||||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
|
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
import io.element.android.libraries.matrix.ui.media.AvatarAction
|
||||||
|
|
@ -23,96 +26,93 @@ import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.ensureCalledOnce
|
import io.element.android.tests.testutils.ensureCalledOnce
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
import org.junit.Rule
|
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
import org.junit.rules.TestRule
|
|
||||||
import org.junit.runner.RunWith
|
import org.junit.runner.RunWith
|
||||||
|
|
||||||
@RunWith(AndroidJUnit4::class)
|
@RunWith(AndroidJUnit4::class)
|
||||||
class EditUserProfileViewTest {
|
class EditUserProfileViewTest {
|
||||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on back emits the expected event`() {
|
fun `clicking on back emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
||||||
rule.setEditUserProfileView(
|
setEditUserProfileView(
|
||||||
aEditUserProfileState(
|
aEditUserProfileState(
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.pressBack()
|
pressBack()
|
||||||
eventsRecorder.assertSingle(EditUserProfileEvent.Exit)
|
eventsRecorder.assertSingle(EditUserProfileEvent.Exit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on save from the exit confirmation dialog emits the expected event`() {
|
fun `clicking on save from the exit confirmation dialog emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
||||||
rule.setEditUserProfileView(
|
setEditUserProfileView(
|
||||||
aEditUserProfileState(
|
aEditUserProfileState(
|
||||||
saveAction = AsyncAction.ConfirmingCancellation,
|
saveAction = AsyncAction.ConfirmingCancellation,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_save, inDialog = true)
|
clickOn(CommonStrings.action_save, inDialog = true)
|
||||||
eventsRecorder.assertSingle(EditUserProfileEvent.Save)
|
eventsRecorder.assertSingle(EditUserProfileEvent.Save)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on discard exit emits the expected event`() {
|
fun `clicking on discard exit emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
||||||
rule.setEditUserProfileView(
|
setEditUserProfileView(
|
||||||
aEditUserProfileState(
|
aEditUserProfileState(
|
||||||
saveAction = AsyncAction.ConfirmingCancellation,
|
saveAction = AsyncAction.ConfirmingCancellation,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_discard)
|
clickOn(CommonStrings.action_discard)
|
||||||
eventsRecorder.assertSingle(EditUserProfileEvent.Exit)
|
eventsRecorder.assertSingle(EditUserProfileEvent.Exit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on save emits the expected event`() {
|
fun `clicking on save emits the expected event`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
||||||
rule.setEditUserProfileView(
|
setEditUserProfileView(
|
||||||
aEditUserProfileState(
|
aEditUserProfileState(
|
||||||
saveButtonEnabled = true,
|
saveButtonEnabled = true,
|
||||||
saveAction = AsyncAction.Uninitialized,
|
saveAction = AsyncAction.Uninitialized,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
rule.clickOn(CommonStrings.action_save)
|
clickOn(CommonStrings.action_save)
|
||||||
eventsRecorder.assertSingle(EditUserProfileEvent.Save)
|
eventsRecorder.assertSingle(EditUserProfileEvent.Save)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `clicking on avatar opens the bottom sheet dialog`() {
|
fun `clicking on avatar opens the bottom sheet dialog`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
val eventsRecorder = EventsRecorder<EditUserProfileEvent>()
|
||||||
val actions = listOf(
|
val actions = listOf(
|
||||||
AvatarAction.TakePhoto,
|
AvatarAction.TakePhoto,
|
||||||
AvatarAction.ChoosePhoto,
|
AvatarAction.ChoosePhoto,
|
||||||
AvatarAction.Remove,
|
AvatarAction.Remove,
|
||||||
)
|
)
|
||||||
rule.setEditUserProfileView(
|
setEditUserProfileView(
|
||||||
aEditUserProfileState(
|
aEditUserProfileState(
|
||||||
saveAction = AsyncAction.Uninitialized,
|
saveAction = AsyncAction.Uninitialized,
|
||||||
avatarActions = actions,
|
avatarActions = actions,
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
val contentDescription = rule.activity.getString(CommonStrings.a11y_avatar)
|
val resources = activity!!.resources
|
||||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
val contentDescription = resources.getString(CommonStrings.a11y_avatar)
|
||||||
|
onNodeWithContentDescription(contentDescription).performClick()
|
||||||
// Assert that the actions are displayed
|
// Assert that the actions are displayed
|
||||||
actions.forEach { action ->
|
actions.forEach { action ->
|
||||||
val text = rule.activity.getString(action.titleResId)
|
val text = resources.getString(action.titleResId)
|
||||||
rule.onNodeWithText(text).assertExists()
|
onNodeWithText(text).assertExists()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `success invokes the expected callback`() {
|
fun `success invokes the expected callback`() = runAndroidComposeUiTest {
|
||||||
val eventsRecorder = EventsRecorder<EditUserProfileEvent>(expectEvents = false)
|
val eventsRecorder = EventsRecorder<EditUserProfileEvent>(expectEvents = false)
|
||||||
ensureCalledOnce { callback ->
|
ensureCalledOnce { callback ->
|
||||||
rule.setEditUserProfileView(
|
setEditUserProfileView(
|
||||||
aEditUserProfileState(
|
aEditUserProfileState(
|
||||||
saveAction = AsyncAction.Success(Unit),
|
saveAction = AsyncAction.Success(Unit),
|
||||||
eventSink = eventsRecorder,
|
eventSink = eventsRecorder,
|
||||||
|
|
@ -123,7 +123,7 @@ class EditUserProfileViewTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setEditUserProfileView(
|
private fun AndroidComposeUiTest<ComponentActivity>.setEditUserProfileView(
|
||||||
state: EditUserProfileState,
|
state: EditUserProfileState,
|
||||||
onEditProfileSuccess: () -> Unit = EnsureNeverCalled(),
|
onEditProfileSuccess: () -> Unit = EnsureNeverCalled(),
|
||||||
) {
|
) {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue