Merge branch 'develop' into feature/bma/removeExternalCallSupport
This commit is contained in:
commit
e21276f323
122 changed files with 2266 additions and 2352 deletions
|
|
@ -44,14 +44,12 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(libs.serialization.json)
|
||||
api(projects.libraries.sessionStorage.api)
|
||||
implementation(libs.coroutines.core)
|
||||
api(projects.libraries.architecture)
|
||||
implementation(libs.serialization.json)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
|
||||
testCommonDependencies(libs)
|
||||
testImplementation(projects.libraries.matrix.test)
|
||||
|
|
|
|||
|
|
@ -32,10 +32,12 @@ dependencies {
|
|||
|
||||
implementation(projects.appconfig)
|
||||
implementation(projects.libraries.androidutils)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.featureflag.api)
|
||||
implementation(projects.libraries.network)
|
||||
implementation(projects.libraries.preferences.api)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
implementation(projects.libraries.workmanager.api)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ dependencies {
|
|||
api(projects.libraries.matrix.api)
|
||||
api(libs.coroutines.core)
|
||||
implementation(libs.coroutines.test)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.services.analytics.api)
|
||||
implementation(projects.tests.testutils)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
|
|
|
|||
|
|
@ -19,8 +19,10 @@ android {
|
|||
setupDependencyInjection()
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.matrixmedia.api)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
implementation(projects.libraries.designsystem)
|
||||
implementation(libs.coil.compose)
|
||||
implementation(libs.coil.gif)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.coroutines.core)
|
||||
api(projects.libraries.mediaupload.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.tests.testutils)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ import io.element.android.libraries.matrix.api.room.BaseRoom
|
|||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.matrix.api.timeline.item.event.toEventOrTransactionId
|
||||
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
|
@ -27,10 +28,11 @@ import kotlinx.coroutines.flow.launchIn
|
|||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.onCompletion
|
||||
import kotlinx.coroutines.flow.onEach
|
||||
import timber.log.Timber
|
||||
import java.util.concurrent.atomic.AtomicBoolean
|
||||
|
||||
interface MediaGalleryDataSource {
|
||||
fun start()
|
||||
fun start(coroutineScope: CoroutineScope)
|
||||
fun groupedMediaItemsFlow(): Flow<AsyncData<GroupedMediaItems>>
|
||||
fun getLastData(): AsyncData<GroupedMediaItems>
|
||||
suspend fun loadMore(direction: Timeline.PaginationDirection)
|
||||
|
|
@ -58,7 +60,7 @@ class TimelineMediaGalleryDataSource(
|
|||
private val isStarted = AtomicBoolean(false)
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
override fun start() {
|
||||
override fun start(coroutineScope: CoroutineScope) {
|
||||
if (!isStarted.compareAndSet(false, true)) {
|
||||
return
|
||||
}
|
||||
|
|
@ -96,9 +98,12 @@ class TimelineMediaGalleryDataSource(
|
|||
groupedMediaItemsFlow.emit(AsyncData.Success(groupedMediaItems))
|
||||
}
|
||||
.onCompletion {
|
||||
timeline?.close()
|
||||
timeline?.let {
|
||||
Timber.d("Timeline media gallery data source flow completed for room ${room.roomId}, closing timeline")
|
||||
it.close()
|
||||
}
|
||||
}
|
||||
.launchIn(room.roomCoroutineScope)
|
||||
.launchIn(coroutineScope)
|
||||
}
|
||||
|
||||
override suspend fun loadMore(direction: Timeline.PaginationDirection) {
|
||||
|
|
|
|||
|
|
@ -78,7 +78,7 @@ class MediaGalleryPresenter(
|
|||
.collectAsState(AsyncData.Uninitialized)
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
mediaGalleryDataSource.start()
|
||||
mediaGalleryDataSource.start(this)
|
||||
}
|
||||
|
||||
val permissions by room.permissionsAsState(MediaPermissions.DEFAULT) { perms ->
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ import kotlinx.collections.immutable.ImmutableList
|
|||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
import kotlinx.coroutines.CoroutineDispatcher
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.withContext
|
||||
|
|
@ -62,11 +63,12 @@ class MediaViewerDataSource(
|
|||
private val localMediaStates: MutableMap<String, MutableState<AsyncData<LocalMedia>>> =
|
||||
mutableMapOf()
|
||||
|
||||
fun setup() {
|
||||
galleryDataSource.start()
|
||||
fun setup(coroutineScope: CoroutineScope) {
|
||||
galleryDataSource.start(coroutineScope)
|
||||
}
|
||||
|
||||
fun dispose() {
|
||||
Timber.d("Disposing MediaViewerDataSource, closing ${mediaFiles.size} media files")
|
||||
mediaFiles.forEach { it.close() }
|
||||
mediaFiles.clear()
|
||||
localMediaStates.clear()
|
||||
|
|
|
|||
|
|
@ -88,7 +88,7 @@ class MediaViewerPresenter(
|
|||
var mediaBottomSheetState by remember { mutableStateOf<MediaBottomSheetState>(MediaBottomSheetState.Hidden) }
|
||||
|
||||
DisposableEffect(Unit) {
|
||||
dataSource.setup()
|
||||
dataSource.setup(coroutineScope)
|
||||
onDispose {
|
||||
dataSource.dispose()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,12 +20,13 @@ import io.element.android.libraries.mediaviewer.impl.datasource.MediaGalleryData
|
|||
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
|
||||
import io.element.android.libraries.mediaviewer.impl.model.MediaItem
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.flowOf
|
||||
|
||||
class SingleMediaGalleryDataSource(
|
||||
private val data: GroupedMediaItems,
|
||||
) : MediaGalleryDataSource {
|
||||
override fun start() = Unit
|
||||
override fun start(coroutineScope: CoroutineScope) = Unit
|
||||
override fun groupedMediaItemsFlow() = flowOf(AsyncData.Success(data))
|
||||
override fun getLastData(): AsyncData<GroupedMediaItems> = AsyncData.Success(data)
|
||||
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.core.EventId
|
|||
import io.element.android.libraries.matrix.api.timeline.Timeline
|
||||
import io.element.android.libraries.mediaviewer.impl.model.GroupedMediaItems
|
||||
import io.element.android.tests.testutils.lambda.lambdaError
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
|
||||
|
|
@ -21,7 +22,7 @@ class FakeMediaGalleryDataSource(
|
|||
private val loadMoreLambda: (Timeline.PaginationDirection) -> Unit = { lambdaError() },
|
||||
private val deleteItemLambda: (EventId) -> Unit = { lambdaError() },
|
||||
) : MediaGalleryDataSource {
|
||||
override fun start() = startLambda()
|
||||
override fun start(coroutineScope: CoroutineScope) = startLambda()
|
||||
|
||||
private val groupedMediaItemsFlow = MutableSharedFlow<AsyncData<GroupedMediaItems>>(
|
||||
replay = 1
|
||||
|
|
|
|||
|
|
@ -80,7 +80,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
sut.start()
|
||||
sut.start(backgroundScope)
|
||||
assertThat(sut.getLastData()).isEqualTo(AsyncData.Uninitialized)
|
||||
sut.groupedMediaItemsFlow().test {
|
||||
assertThat(awaitItem().isLoading()).isTrue()
|
||||
|
|
@ -95,7 +95,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
)
|
||||
assertThat(sut.getLastData().isSuccess()).isTrue()
|
||||
// Also test that starting again should have no effect
|
||||
sut.start()
|
||||
sut.start(backgroundScope)
|
||||
}
|
||||
}
|
||||
// Ensure that the timeline has been closed on flow completion
|
||||
|
|
@ -117,7 +117,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
sut.start()
|
||||
sut.start(backgroundScope)
|
||||
sut.groupedMediaItemsFlow().test {
|
||||
skipItems(2)
|
||||
sut.loadMore(Timeline.PaginationDirection.BACKWARDS)
|
||||
|
|
@ -140,7 +140,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
sut.start()
|
||||
sut.start(backgroundScope)
|
||||
sut.groupedMediaItemsFlow().test {
|
||||
skipItems(2)
|
||||
sut.deleteItem(AN_EVENT_ID)
|
||||
|
|
@ -159,7 +159,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
sut.start()
|
||||
sut.start(backgroundScope)
|
||||
sut.groupedMediaItemsFlow().test {
|
||||
assertThat(awaitItem().isLoading()).isTrue()
|
||||
assertThat(sut.getLastData().isLoading()).isTrue()
|
||||
|
|
@ -181,7 +181,7 @@ class TimelineMediaGalleryDataSourceTest {
|
|||
roomCoroutineScope = backgroundScope,
|
||||
)
|
||||
)
|
||||
sut.start()
|
||||
sut.start(backgroundScope)
|
||||
sut.groupedMediaItemsFlow().test {
|
||||
assertThat(awaitItem().isLoading()).isTrue()
|
||||
assertThat(sut.getLastData().isLoading()).isTrue()
|
||||
|
|
|
|||
|
|
@ -6,12 +6,15 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.libraries.mediaviewer.impl.details
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -21,43 +24,38 @@ import io.element.android.tests.testutils.clickOn
|
|||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.setSafeContent
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MediaDeleteConfirmationBottomSheetTest {
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on Cancel invokes expected callback`() {
|
||||
fun `clicking on Cancel invokes expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aMediaBottomSheetStateDeleteConfirmation()
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setMediaDeleteConfirmationBottomSheet(
|
||||
setMediaDeleteConfirmationBottomSheet(
|
||||
state = state,
|
||||
onDismiss = callback,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
clickOn(CommonStrings.action_cancel)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on Remove invokes expected callback`() {
|
||||
fun `clicking on Remove invokes expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aMediaBottomSheetStateDeleteConfirmation()
|
||||
ensureCalledOnceWithParam(state.eventId) { callback ->
|
||||
rule.setMediaDeleteConfirmationBottomSheet(
|
||||
setMediaDeleteConfirmationBottomSheet(
|
||||
state = state,
|
||||
onDelete = callback,
|
||||
)
|
||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.action_remove)).assertExists()
|
||||
rule.clickOn(CommonStrings.action_remove)
|
||||
onNodeWithText(activity!!.getString(CommonStrings.action_remove)).assertExists()
|
||||
clickOn(CommonStrings.action_remove)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMediaDeleteConfirmationBottomSheet(
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setMediaDeleteConfirmationBottomSheet(
|
||||
state: MediaBottomSheetState.DeleteConfirmation,
|
||||
onDelete: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onDismiss: () -> Unit = EnsureNeverCalled(),
|
||||
|
|
|
|||
|
|
@ -6,12 +6,15 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.libraries.mediaviewer.impl.details
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.ui.strings.CommonStrings
|
||||
|
|
@ -20,97 +23,92 @@ import io.element.android.tests.testutils.EnsureNeverCalledWithParam
|
|||
import io.element.android.tests.testutils.clickOn
|
||||
import io.element.android.tests.testutils.ensureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.setSafeContent
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MediaDetailsBottomSheetTest {
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1024dp")
|
||||
fun `clicking on View in timeline invokes expected callback`() {
|
||||
fun `clicking on View in timeline invokes expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aMediaBottomSheetStateDetails()
|
||||
ensureCalledOnceWithParam(state.eventId) { callback ->
|
||||
rule.setMediaDetailsBottomSheet(
|
||||
setMediaDetailsBottomSheet(
|
||||
state = state,
|
||||
onViewInTimeline = callback,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_view_in_timeline)
|
||||
clickOn(CommonStrings.action_view_in_timeline)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1024dp")
|
||||
fun `clicking on Share invokes expected callback`() {
|
||||
fun `clicking on Share invokes expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aMediaBottomSheetStateDetails()
|
||||
ensureCalledOnceWithParam(state.eventId) { callback ->
|
||||
rule.setMediaDetailsBottomSheet(
|
||||
setMediaDetailsBottomSheet(
|
||||
state = state,
|
||||
onShare = callback,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_share)
|
||||
clickOn(CommonStrings.action_share)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1024dp")
|
||||
fun `clicking on Forward invokes expected callback`() {
|
||||
fun `clicking on Forward invokes expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aMediaBottomSheetStateDetails()
|
||||
ensureCalledOnceWithParam(state.eventId) { callback ->
|
||||
rule.setMediaDetailsBottomSheet(
|
||||
setMediaDetailsBottomSheet(
|
||||
state = state,
|
||||
onForward = callback,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_forward)
|
||||
clickOn(CommonStrings.action_forward)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
@Config(qualifiers = "h1024dp")
|
||||
fun `clicking on Download invokes expected callback`() {
|
||||
fun `clicking on Download invokes expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aMediaBottomSheetStateDetails()
|
||||
ensureCalledOnceWithParam(state.eventId) { callback ->
|
||||
rule.setMediaDetailsBottomSheet(
|
||||
setMediaDetailsBottomSheet(
|
||||
state = state,
|
||||
onDownload = callback,
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_download)
|
||||
clickOn(CommonStrings.action_download)
|
||||
}
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on Delete invokes expected callback`() {
|
||||
fun `clicking on Delete invokes expected callback`() = runAndroidComposeUiTest {
|
||||
val state = aMediaBottomSheetStateDetails()
|
||||
ensureCalledOnceWithParam(state.eventId) { callback ->
|
||||
rule.setMediaDetailsBottomSheet(
|
||||
setMediaDetailsBottomSheet(
|
||||
state = state,
|
||||
onDelete = callback,
|
||||
)
|
||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.action_delete)).assertExists()
|
||||
rule.clickOn(CommonStrings.action_delete)
|
||||
onNodeWithText(activity!!.getString(CommonStrings.action_delete)).assertExists()
|
||||
clickOn(CommonStrings.action_delete)
|
||||
}
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `Remove is not present if canDelete is false`() {
|
||||
fun `Remove is not present if canDelete is false`() = runAndroidComposeUiTest {
|
||||
val state = aMediaBottomSheetStateDetails(
|
||||
canDelete = false,
|
||||
)
|
||||
rule.setMediaDetailsBottomSheet(
|
||||
setMediaDetailsBottomSheet(
|
||||
state = state,
|
||||
)
|
||||
rule.onNodeWithText(rule.activity.getString(CommonStrings.action_remove)).assertDoesNotExist()
|
||||
onNodeWithText(activity!!.getString(CommonStrings.action_remove)).assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMediaDetailsBottomSheet(
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setMediaDetailsBottomSheet(
|
||||
state: MediaBottomSheetState.Details,
|
||||
onViewInTimeline: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
onShare: (EventId) -> Unit = EnsureNeverCalledWithParam(),
|
||||
|
|
|
|||
|
|
@ -50,7 +50,7 @@ class MediaViewerDataSourceTest {
|
|||
val sut = createMediaViewerDataSource(
|
||||
galleryDataSource = galleryDataSource,
|
||||
)
|
||||
sut.setup()
|
||||
sut.setup(backgroundScope)
|
||||
startLambda.assertions().isCalledOnce()
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -6,18 +6,21 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.libraries.mediaviewer.impl.viewer
|
||||
|
||||
import android.net.Uri
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.assertHasClickAction
|
||||
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.performClick
|
||||
import androidx.compose.ui.test.performTouchInput
|
||||
import androidx.compose.ui.test.swipeDown
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.mediaviewer.impl.details.aMediaBottomSheetStateDetails
|
||||
|
|
@ -30,30 +33,26 @@ import io.element.android.tests.testutils.ensureCalledOnce
|
|||
import io.element.android.tests.testutils.pressBack
|
||||
import io.element.android.tests.testutils.setSafeContent
|
||||
import io.mockk.mockk
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MediaViewerViewTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
private val mockMediaUrl: Uri = mockk("localMediaUri")
|
||||
|
||||
@Test
|
||||
fun `clicking on back invokes expected callback`() {
|
||||
fun `clicking on back invokes expected callback`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<MediaViewerEvent>()
|
||||
val state = aMediaViewerState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setMediaViewerView(
|
||||
setMediaViewerView(
|
||||
state = state,
|
||||
onBackClick = callback,
|
||||
)
|
||||
rule.pressBack()
|
||||
pressBack()
|
||||
}
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
|
|
@ -103,16 +102,16 @@ class MediaViewerViewTest {
|
|||
data: MediaViewerPageData.MediaViewerData,
|
||||
@StringRes contentDescriptionRes: Int,
|
||||
expectedEvent: MediaViewerEvent,
|
||||
) {
|
||||
) = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<MediaViewerEvent>()
|
||||
rule.setMediaViewerView(
|
||||
setMediaViewerView(
|
||||
aMediaViewerState(
|
||||
listData = listOf(data),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
val contentDescription = rule.activity.getString(contentDescriptionRes)
|
||||
rule.onNodeWithContentDescription(contentDescription).performClick()
|
||||
val contentDescription = activity!!.getString(contentDescriptionRes)
|
||||
onNodeWithContentDescription(contentDescription).performClick()
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
MediaViewerEvent.OnNavigateTo(0),
|
||||
|
|
@ -159,16 +158,16 @@ class MediaViewerViewTest {
|
|||
data: MediaViewerPageData.MediaViewerData,
|
||||
@StringRes textRes: Int,
|
||||
expectedEvent: MediaViewerEvent,
|
||||
) {
|
||||
) = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<MediaViewerEvent>()
|
||||
rule.setMediaViewerView(
|
||||
setMediaViewerView(
|
||||
aMediaViewerState(
|
||||
listData = listOf(data),
|
||||
mediaBottomSheetState = aMediaBottomSheetStateDetails(),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
rule.clickOn(textRes)
|
||||
clickOn(textRes)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
MediaViewerEvent.OnNavigateTo(0),
|
||||
|
|
@ -179,24 +178,25 @@ class MediaViewerViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on image hides the overlay`() {
|
||||
fun `clicking on image hides the overlay`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<MediaViewerEvent>()
|
||||
val state = aMediaViewerState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
rule.setMediaViewerView(
|
||||
setMediaViewerView(
|
||||
state = state,
|
||||
)
|
||||
// Ensure that the action are visible
|
||||
val contentDescription = rule.activity.getString(CommonStrings.action_share)
|
||||
rule.onNodeWithContentDescription(contentDescription)
|
||||
val resources = activity!!.resources
|
||||
val contentDescription = resources.getString(CommonStrings.action_share)
|
||||
onNodeWithContentDescription(contentDescription)
|
||||
.assertExists()
|
||||
.assertHasClickAction()
|
||||
val imageContentDescription = rule.activity.getString(CommonStrings.common_image)
|
||||
rule.onNodeWithContentDescription(imageContentDescription).performClick()
|
||||
val imageContentDescription = resources.getString(CommonStrings.common_image)
|
||||
onNodeWithContentDescription(imageContentDescription).performClick()
|
||||
// Give time for the animation (? since even by removing AnimatedVisibility it still fails)
|
||||
rule.mainClock.advanceTimeBy(1_000)
|
||||
rule.onNodeWithContentDescription(contentDescription)
|
||||
mainClock.advanceTimeBy(1_000)
|
||||
onNodeWithContentDescription(contentDescription)
|
||||
.assertDoesNotExist()
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
|
|
@ -207,19 +207,19 @@ class MediaViewerViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `clicking swipe on the image invokes the expected callback`() {
|
||||
fun `clicking swipe on the image invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<MediaViewerEvent>()
|
||||
val state = aMediaViewerState(
|
||||
eventSink = eventsRecorder
|
||||
)
|
||||
ensureCalledOnce { callback ->
|
||||
rule.setMediaViewerView(
|
||||
setMediaViewerView(
|
||||
state = state,
|
||||
onBackClick = callback,
|
||||
)
|
||||
val imageContentDescription = rule.activity.getString(CommonStrings.common_image)
|
||||
rule.onNodeWithContentDescription(imageContentDescription).performTouchInput { swipeDown(startY = centerY) }
|
||||
rule.mainClock.advanceTimeBy(1_000)
|
||||
val imageContentDescription = activity!!.getString(CommonStrings.common_image)
|
||||
onNodeWithContentDescription(imageContentDescription).performTouchInput { swipeDown(startY = centerY) }
|
||||
mainClock.advanceTimeBy(1_000)
|
||||
}
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
|
|
@ -230,18 +230,18 @@ class MediaViewerViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `error case, click on retry emits the expected Event`() {
|
||||
fun `error case, click on retry emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<MediaViewerEvent>()
|
||||
val data = aMediaViewerPageData(
|
||||
downloadedMedia = AsyncData.Failure(IllegalStateException("error")),
|
||||
)
|
||||
rule.setMediaViewerView(
|
||||
setMediaViewerView(
|
||||
aMediaViewerState(
|
||||
listData = listOf(data),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_retry)
|
||||
clickOn(CommonStrings.action_retry)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
MediaViewerEvent.OnNavigateTo(0),
|
||||
|
|
@ -252,18 +252,18 @@ class MediaViewerViewTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `error case, click on cancel emits the expected Event`() {
|
||||
fun `error case, click on cancel emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<MediaViewerEvent>()
|
||||
val data = aMediaViewerPageData(
|
||||
downloadedMedia = AsyncData.Failure(IllegalStateException("error")),
|
||||
)
|
||||
rule.setMediaViewerView(
|
||||
setMediaViewerView(
|
||||
aMediaViewerState(
|
||||
listData = listOf(data),
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
rule.clickOn(CommonStrings.action_cancel)
|
||||
clickOn(CommonStrings.action_cancel)
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
MediaViewerEvent.OnNavigateTo(0),
|
||||
|
|
@ -274,7 +274,7 @@ class MediaViewerViewTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMediaViewerView(
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setMediaViewerView(
|
||||
state: MediaViewerState,
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -37,9 +37,9 @@ class SingleMediaGalleryDataSourceTest {
|
|||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `function start is no op`() {
|
||||
fun `function start is no op`() = runTest {
|
||||
val sut = SingleMediaGalleryDataSource(aGroupedMediaItems())
|
||||
sut.start()
|
||||
sut.start(backgroundScope)
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ android {
|
|||
|
||||
dependencies {
|
||||
api(projects.libraries.mediaviewer.impl)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.tests.testutils)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
|
|
|
|||
|
|
@ -25,4 +25,5 @@ dependencies {
|
|||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ dependencies {
|
|||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.push.api)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
implementation(projects.libraries.uiStrings)
|
||||
implementation(projects.libraries.troubleshoot.api)
|
||||
implementation(projects.services.toolbox.api)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ setupDependencyInjection()
|
|||
|
||||
dependencies {
|
||||
api(projects.libraries.recentemojis.api)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(libs.kotlinx.collections.immutable)
|
||||
implementation(libs.matrix.emojibase.bindings)
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ android {
|
|||
}
|
||||
|
||||
dependencies {
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -6,12 +6,15 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.libraries.textcomposer.impl.components.markdown
|
||||
|
||||
import android.widget.EditText
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import androidx.core.text.getSpans
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
|
|
@ -32,66 +35,54 @@ import io.element.android.libraries.textcomposer.model.SuggestionType
|
|||
import io.element.android.libraries.textcomposer.model.aMarkdownTextEditorState
|
||||
import io.element.android.tests.testutils.EnsureCalledOnceWithParam
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class MarkdownTextInputTest {
|
||||
@get:Rule val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `when user types onTyping is triggered with value 'true'`() = runTest {
|
||||
fun `when user types onTyping is triggered with value 'true'`() = runAndroidComposeUiTest {
|
||||
val state = aMarkdownTextEditorState(initialFocus = true)
|
||||
val onTyping = EnsureCalledOnceWithParam(expectedParam = true, result = Unit)
|
||||
rule.setMarkdownTextInput(state = state, onTyping = onTyping)
|
||||
rule.activityRule.scenario.onActivity {
|
||||
it.findEditor().setText("Test")
|
||||
}
|
||||
rule.awaitIdle()
|
||||
setMarkdownTextInput(state = state, onTyping = onTyping)
|
||||
activity!!.findEditor().setText("Test")
|
||||
awaitIdle()
|
||||
onTyping.assertSuccess()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when user removes text onTyping is triggered with value 'false'`() = runTest {
|
||||
fun `when user removes text onTyping is triggered with value 'false'`() = runAndroidComposeUiTest {
|
||||
val state = aMarkdownTextEditorState(initialFocus = true)
|
||||
val onTyping = EventsRecorder<Boolean>()
|
||||
rule.setMarkdownTextInput(state = state, onTyping = onTyping)
|
||||
rule.activityRule.scenario.onActivity {
|
||||
val editText = it.findEditor()
|
||||
editText.setText("Test")
|
||||
editText.setText("")
|
||||
editText.setText(null)
|
||||
}
|
||||
rule.awaitIdle()
|
||||
setMarkdownTextInput(state = state, onTyping = onTyping)
|
||||
val editText = activity!!.findEditor()
|
||||
editText.setText("Test")
|
||||
editText.setText("")
|
||||
editText.setText(null)
|
||||
awaitIdle()
|
||||
onTyping.assertList(listOf(true, false, false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when user types something that's not a mention onSuggestionReceived is triggered with 'null'`() = runTest {
|
||||
fun `when user types something that's not a mention onSuggestionReceived is triggered with 'null'`() = runAndroidComposeUiTest {
|
||||
val state = aMarkdownTextEditorState(initialFocus = true)
|
||||
val onSuggestionReceived = EventsRecorder<Suggestion?>()
|
||||
rule.setMarkdownTextInput(state = state, onSuggestionReceived = onSuggestionReceived)
|
||||
rule.activityRule.scenario.onActivity {
|
||||
it.findEditor().setText("Test")
|
||||
}
|
||||
rule.awaitIdle()
|
||||
setMarkdownTextInput(state = state, onSuggestionReceived = onSuggestionReceived)
|
||||
activity!!.findEditor().setText("Test")
|
||||
awaitIdle()
|
||||
onSuggestionReceived.assertSingle(null)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when user types something that's a mention onSuggestionReceived is triggered a real value`() = runTest {
|
||||
fun `when user types something that's a mention onSuggestionReceived is triggered a real value`() = runAndroidComposeUiTest {
|
||||
val state = aMarkdownTextEditorState(initialFocus = true)
|
||||
val onSuggestionReceived = EventsRecorder<Suggestion?>()
|
||||
rule.setMarkdownTextInput(state = state, onSuggestionReceived = onSuggestionReceived)
|
||||
rule.activityRule.scenario.onActivity {
|
||||
it.findEditor().setText("@")
|
||||
it.findEditor().setText("#")
|
||||
it.findEditor().setText("/")
|
||||
}
|
||||
rule.awaitIdle()
|
||||
setMarkdownTextInput(state = state, onSuggestionReceived = onSuggestionReceived)
|
||||
val editor = activity!!.findEditor()
|
||||
editor.setText("@")
|
||||
editor.setText("#")
|
||||
editor.setText("/")
|
||||
awaitIdle()
|
||||
onSuggestionReceived.assertList(
|
||||
listOf(
|
||||
// User mention suggestion
|
||||
|
|
@ -105,69 +96,59 @@ class MarkdownTextInputTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `when the selection changes in the UI the state is updated`() = runTest {
|
||||
fun `when the selection changes in the UI the state is updated`() = runAndroidComposeUiTest {
|
||||
val state = aMarkdownTextEditorState(initialText = "Test", initialFocus = true)
|
||||
rule.setMarkdownTextInput(state = state)
|
||||
rule.activityRule.scenario.onActivity {
|
||||
val editor = it.findEditor()
|
||||
editor.setSelection(2)
|
||||
}
|
||||
rule.awaitIdle()
|
||||
setMarkdownTextInput(state = state)
|
||||
val editor = activity!!.findEditor()
|
||||
editor.setSelection(2)
|
||||
awaitIdle()
|
||||
// Selection is updated
|
||||
assertThat(state.selection).isEqualTo(2..2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when the selection state changes in the view is updated`() = runTest {
|
||||
fun `when the selection state changes in the view is updated`() = runAndroidComposeUiTest {
|
||||
val state = aMarkdownTextEditorState(initialText = "Test", initialFocus = true)
|
||||
rule.setMarkdownTextInput(state = state)
|
||||
var editor: EditText? = null
|
||||
rule.activityRule.scenario.onActivity {
|
||||
editor = it.findEditor()
|
||||
state.selection = 2..2
|
||||
}
|
||||
rule.awaitIdle()
|
||||
setMarkdownTextInput(state = state)
|
||||
val editor = activity!!.findEditor()
|
||||
state.selection = 2..2
|
||||
awaitIdle()
|
||||
// Selection state is updated
|
||||
assertThat(editor?.selectionStart).isEqualTo(2)
|
||||
assertThat(editor?.selectionEnd).isEqualTo(2)
|
||||
assertThat(editor.selectionStart).isEqualTo(2)
|
||||
assertThat(editor.selectionEnd).isEqualTo(2)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `when the view focus changes the state is updated`() = runTest {
|
||||
fun `when the view focus changes the state is updated`() = runAndroidComposeUiTest {
|
||||
val state = aMarkdownTextEditorState(initialText = "Test", initialFocus = false)
|
||||
rule.setMarkdownTextInput(state = state)
|
||||
rule.activityRule.scenario.onActivity {
|
||||
val editor = it.findEditor()
|
||||
editor.requestFocus()
|
||||
}
|
||||
setMarkdownTextInput(state = state)
|
||||
val editor = activity!!.findEditor()
|
||||
editor.requestFocus()
|
||||
// Focus state is updated
|
||||
assertThat(state.hasFocus).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `inserting a mention replaces the existing text with a span`() = runTest {
|
||||
fun `inserting a mention replaces the existing text with a span`() = runAndroidComposeUiTest {
|
||||
val permalinkParser = FakePermalinkParser(result = { PermalinkData.UserLink(A_SESSION_ID) })
|
||||
val state = aMarkdownTextEditorState(initialText = "@", initialFocus = true)
|
||||
state.currentSuggestion = Suggestion(0, 1, SuggestionType.Mention, "")
|
||||
rule.setMarkdownTextInput(state = state)
|
||||
var editor: EditText? = null
|
||||
rule.activityRule.scenario.onActivity {
|
||||
editor = it.findEditor()
|
||||
state.insertSuggestion(
|
||||
ResolvedSuggestion.Member(roomMember = aRoomMember()),
|
||||
aMentionSpanProvider(permalinkParser),
|
||||
)
|
||||
}
|
||||
rule.awaitIdle()
|
||||
setMarkdownTextInput(state = state)
|
||||
val editor = activity!!.findEditor()
|
||||
state.insertSuggestion(
|
||||
ResolvedSuggestion.Member(roomMember = aRoomMember()),
|
||||
aMentionSpanProvider(permalinkParser),
|
||||
)
|
||||
awaitIdle()
|
||||
|
||||
// Text is replaced with a placeholder
|
||||
assertThat(editor?.editableText.toString()).isEqualTo("@ ")
|
||||
assertThat(editor.editableText.toString()).isEqualTo("@ ")
|
||||
// The placeholder contains a MentionSpan
|
||||
val mentionSpans = editor?.editableText?.getSpans<MentionSpan>(0, 2).orEmpty()
|
||||
val mentionSpans = editor.editableText?.getSpans<MentionSpan>(0, 2).orEmpty()
|
||||
assertThat(mentionSpans).isNotEmpty()
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setMarkdownTextInput(
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setMarkdownTextInput(
|
||||
state: MarkdownTextEditorState = aMarkdownTextEditorState(),
|
||||
onTyping: (Boolean) -> Unit = {},
|
||||
onSuggestionReceived: (Suggestion?) -> Unit = {},
|
||||
|
|
|
|||
|
|
@ -6,60 +6,58 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.ensureCalledOnce
|
||||
import io.element.android.tests.testutils.pressBack
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
import org.robolectric.annotation.Config
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class TroubleshootNotificationsViewTest {
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `press menu back invokes the expected callback`() {
|
||||
fun `press menu back invokes the expected callback`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<TroubleshootNotificationsEvents>(expectEvents = false)
|
||||
ensureCalledOnce {
|
||||
rule.setTroubleshootNotificationsView(
|
||||
setTroubleshootNotificationsView(
|
||||
state = aTroubleshootNotificationsState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
onBackClick = it,
|
||||
)
|
||||
rule.pressBack()
|
||||
pressBack()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on run test emits the expected Event`() {
|
||||
fun `clicking on run test emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<TroubleshootNotificationsEvents>()
|
||||
rule.setTroubleshootNotificationsView(
|
||||
setTroubleshootNotificationsView(
|
||||
aTroubleshootNotificationsState(
|
||||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
rule.onNodeWithText("Run tests").performClick()
|
||||
onNodeWithText("Run tests").performClick()
|
||||
eventsRecorder.assertSingle(TroubleshootNotificationsEvents.StartTests)
|
||||
}
|
||||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on run test again emits the expected Event`() {
|
||||
fun `clicking on run test again emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<TroubleshootNotificationsEvents>()
|
||||
rule.setTroubleshootNotificationsView(
|
||||
setTroubleshootNotificationsView(
|
||||
aTroubleshootNotificationsState(
|
||||
tests = listOf(
|
||||
aTroubleshootTestStateFailure(
|
||||
|
|
@ -69,7 +67,7 @@ class TroubleshootNotificationsViewTest {
|
|||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
rule.onNodeWithText("Run tests again").performClick()
|
||||
onNodeWithText("Run tests again").performClick()
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
TroubleshootNotificationsEvents.RetryFailedTests,
|
||||
|
|
@ -80,9 +78,9 @@ class TroubleshootNotificationsViewTest {
|
|||
|
||||
@Config(qualifiers = "h1024dp")
|
||||
@Test
|
||||
fun `clicking on quick fix emits the expected Event`() {
|
||||
fun `clicking on quick fix emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<TroubleshootNotificationsEvents>()
|
||||
rule.setTroubleshootNotificationsView(
|
||||
setTroubleshootNotificationsView(
|
||||
aTroubleshootNotificationsState(
|
||||
tests = listOf(
|
||||
aTroubleshootTestStateFailure(
|
||||
|
|
@ -92,7 +90,7 @@ class TroubleshootNotificationsViewTest {
|
|||
eventSink = eventsRecorder
|
||||
),
|
||||
)
|
||||
rule.onNodeWithText("Attempt to fix").performClick()
|
||||
onNodeWithText("Attempt to fix").performClick()
|
||||
eventsRecorder.assertList(
|
||||
listOf(
|
||||
TroubleshootNotificationsEvents.RetryFailedTests,
|
||||
|
|
@ -102,7 +100,7 @@ class TroubleshootNotificationsViewTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setTroubleshootNotificationsView(
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setTroubleshootNotificationsView(
|
||||
state: TroubleshootNotificationsState,
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -6,14 +6,17 @@
|
|||
* Please see LICENSE files in the repository root for full details.
|
||||
*/
|
||||
|
||||
@file:OptIn(ExperimentalTestApi::class)
|
||||
|
||||
package io.element.android.libraries.troubleshoot.impl.history
|
||||
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||
import androidx.compose.ui.test.junit4.createAndroidComposeRule
|
||||
import androidx.compose.ui.test.AndroidComposeUiTest
|
||||
import androidx.compose.ui.test.ExperimentalTestApi
|
||||
import androidx.compose.ui.test.onNodeWithContentDescription
|
||||
import androidx.compose.ui.test.onNodeWithText
|
||||
import androidx.compose.ui.test.performClick
|
||||
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_FORMATTED_DATE
|
||||
|
|
@ -23,67 +26,62 @@ import io.element.android.libraries.ui.strings.CommonStrings
|
|||
import io.element.android.tests.testutils.EnsureNeverCalled
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.clickOn
|
||||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
import org.junit.rules.TestRule
|
||||
import org.junit.runner.RunWith
|
||||
|
||||
@RunWith(AndroidJUnit4::class)
|
||||
class PushHistoryViewTest {
|
||||
@get:Rule
|
||||
val rule = createAndroidComposeRule<ComponentActivity>()
|
||||
|
||||
@Test
|
||||
fun `clicking on Reset sends a PushHistoryEvents`() {
|
||||
fun `clicking on Reset sends a PushHistoryEvents`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PushHistoryEvents>()
|
||||
rule.setPushHistoryView(
|
||||
setPushHistoryView(
|
||||
aPushHistoryState(
|
||||
pushCounter = 123,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu)
|
||||
rule.onNodeWithContentDescription(menuContentDescription).performClick()
|
||||
rule.clickOn(CommonStrings.action_reset)
|
||||
val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu)
|
||||
onNodeWithContentDescription(menuContentDescription).performClick()
|
||||
clickOn(CommonStrings.action_reset)
|
||||
eventsRecorder.assertSingle(PushHistoryEvents.Reset(requiresConfirmation = true))
|
||||
// Also check that the push counter is rendered
|
||||
rule.onNodeWithText("123").assertExists()
|
||||
onNodeWithText("123").assertExists()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on show only errors sends a PushHistoryEvents(true)`() {
|
||||
fun `clicking on show only errors sends a PushHistoryEvents(true)`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PushHistoryEvents>()
|
||||
rule.setPushHistoryView(
|
||||
setPushHistoryView(
|
||||
aPushHistoryState(
|
||||
showOnlyErrors = false,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu)
|
||||
rule.onNodeWithContentDescription(menuContentDescription).performClick()
|
||||
rule.onNodeWithText("Show only errors").performClick()
|
||||
val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu)
|
||||
onNodeWithContentDescription(menuContentDescription).performClick()
|
||||
onNodeWithText("Show only errors").performClick()
|
||||
eventsRecorder.assertSingle(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = true))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on show only errors sends a PushHistoryEvents(false)`() {
|
||||
fun `clicking on show only errors sends a PushHistoryEvents(false)`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PushHistoryEvents>()
|
||||
rule.setPushHistoryView(
|
||||
setPushHistoryView(
|
||||
aPushHistoryState(
|
||||
showOnlyErrors = true,
|
||||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
val menuContentDescription = rule.activity.getString(CommonStrings.a11y_user_menu)
|
||||
rule.onNodeWithContentDescription(menuContentDescription).performClick()
|
||||
rule.onNodeWithText("Show only errors").performClick()
|
||||
val menuContentDescription = activity!!.getString(CommonStrings.a11y_user_menu)
|
||||
onNodeWithContentDescription(menuContentDescription).performClick()
|
||||
onNodeWithText("Show only errors").performClick()
|
||||
eventsRecorder.assertSingle(PushHistoryEvents.SetShowOnlyErrors(showOnlyErrors = false))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on an invalid event has no effect`() {
|
||||
fun `clicking on an invalid event has no effect`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PushHistoryEvents>(expectEvents = false)
|
||||
rule.setPushHistoryView(
|
||||
setPushHistoryView(
|
||||
aPushHistoryState(
|
||||
pushHistoryItems = listOf(
|
||||
aPushHistoryItem(
|
||||
|
|
@ -93,14 +91,14 @@ class PushHistoryViewTest {
|
|||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.onNodeWithText(A_FORMATTED_DATE).performClick()
|
||||
onNodeWithText(A_FORMATTED_DATE).performClick()
|
||||
// No callback invoked
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `clicking on a valid event emits the expected Event`() {
|
||||
fun `clicking on a valid event emits the expected Event`() = runAndroidComposeUiTest {
|
||||
val eventsRecorder = EventsRecorder<PushHistoryEvents>()
|
||||
rule.setPushHistoryView(
|
||||
setPushHistoryView(
|
||||
aPushHistoryState(
|
||||
pushHistoryItems = listOf(
|
||||
aPushHistoryItem(
|
||||
|
|
@ -113,7 +111,7 @@ class PushHistoryViewTest {
|
|||
eventSink = eventsRecorder,
|
||||
),
|
||||
)
|
||||
rule.onNodeWithText(A_FORMATTED_DATE).performClick()
|
||||
onNodeWithText(A_FORMATTED_DATE).performClick()
|
||||
eventsRecorder.assertSingle(
|
||||
PushHistoryEvents.NavigateTo(
|
||||
sessionId = A_SESSION_ID,
|
||||
|
|
@ -124,7 +122,7 @@ class PushHistoryViewTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setPushHistoryView(
|
||||
private fun AndroidComposeUiTest<ComponentActivity>.setPushHistoryView(
|
||||
state: PushHistoryState,
|
||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -16,5 +16,6 @@ android {
|
|||
dependencies {
|
||||
implementation(libs.androidx.annotationjvm)
|
||||
implementation(libs.coroutines.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ setupDependencyInjection()
|
|||
dependencies {
|
||||
api(projects.libraries.voiceplayer.api)
|
||||
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.audio.api)
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.di)
|
||||
|
|
|
|||
|
|
@ -15,6 +15,6 @@ android {
|
|||
|
||||
dependencies {
|
||||
api(libs.androidx.workmanager.runtime)
|
||||
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ dependencies {
|
|||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
implementation(projects.libraries.di)
|
||||
implementation(projects.libraries.sessionStorage.api)
|
||||
|
||||
testCommonDependencies(libs, false)
|
||||
testImplementation(projects.libraries.sessionStorage.test)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue