Merge pull request #3618 from element-hq/feature/bma/injectPresenter
Ensure that `Presenter`s do not depend on other presenters.
This commit is contained in:
commit
51ee5bfdce
73 changed files with 849 additions and 1029 deletions
|
|
@ -12,8 +12,8 @@ import androidx.compose.runtime.LaunchedEffect
|
|||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import im.vector.app.features.analytics.plan.SuperProperties
|
||||
import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter
|
||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter
|
||||
import io.element.android.features.rageshake.api.crash.CrashDetectionState
|
||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionState
|
||||
import io.element.android.features.share.api.ShareService
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.matrix.api.SdkMetadata
|
||||
|
|
@ -22,8 +22,8 @@ import io.element.android.services.apperror.api.AppErrorStateService
|
|||
import javax.inject.Inject
|
||||
|
||||
class RootPresenter @Inject constructor(
|
||||
private val crashDetectionPresenter: CrashDetectionPresenter,
|
||||
private val rageshakeDetectionPresenter: RageshakeDetectionPresenter,
|
||||
private val crashDetectionPresenter: Presenter<CrashDetectionState>,
|
||||
private val rageshakeDetectionPresenter: Presenter<RageshakeDetectionState>,
|
||||
private val appErrorStateService: AppErrorStateService,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val shareService: ShareService,
|
||||
|
|
|
|||
|
|
@ -12,17 +12,11 @@ import app.cash.molecule.moleculeFlow
|
|||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.appnav.root.RootPresenter
|
||||
import io.element.android.features.rageshake.impl.crash.DefaultCrashDetectionPresenter
|
||||
import io.element.android.features.rageshake.impl.detection.DefaultRageshakeDetectionPresenter
|
||||
import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter
|
||||
import io.element.android.features.rageshake.test.crash.FakeCrashDataStore
|
||||
import io.element.android.features.rageshake.test.rageshake.FakeRageShake
|
||||
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
|
||||
import io.element.android.features.rageshake.test.screenshot.FakeScreenshotHolder
|
||||
import io.element.android.features.rageshake.api.crash.aCrashDetectionState
|
||||
import io.element.android.features.rageshake.api.detection.aRageshakeDetectionState
|
||||
import io.element.android.features.share.api.ShareService
|
||||
import io.element.android.features.share.test.FakeShareService
|
||||
import io.element.android.libraries.matrix.test.FakeSdkMetadata
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.apperror.api.AppErrorState
|
||||
import io.element.android.services.apperror.api.AppErrorStateService
|
||||
|
|
@ -44,7 +38,6 @@ class RootPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.crashDetectionState.crashDetected).isFalse()
|
||||
}
|
||||
|
|
@ -61,7 +54,7 @@ class RootPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(2)
|
||||
skipItems(1)
|
||||
lambda.assertions().isCalledOnce()
|
||||
}
|
||||
}
|
||||
|
|
@ -76,8 +69,6 @@ class RootPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.errorState).isInstanceOf(AppErrorState.Error::class.java)
|
||||
val initialErrorState = initialState.errorState as AppErrorState.Error
|
||||
|
|
@ -93,25 +84,9 @@ class RootPresenterTest {
|
|||
appErrorService: AppErrorStateService = DefaultAppErrorStateService(),
|
||||
shareService: ShareService = FakeShareService {},
|
||||
): RootPresenter {
|
||||
val crashDataStore = FakeCrashDataStore()
|
||||
val rageshakeDataStore = FakeRageshakeDataStore()
|
||||
val rageshake = FakeRageShake()
|
||||
val screenshotHolder = FakeScreenshotHolder()
|
||||
val crashDetectionPresenter = DefaultCrashDetectionPresenter(
|
||||
buildMeta = aBuildMeta(),
|
||||
crashDataStore = crashDataStore
|
||||
)
|
||||
val rageshakeDetectionPresenter = DefaultRageshakeDetectionPresenter(
|
||||
screenshotHolder = screenshotHolder,
|
||||
rageShake = rageshake,
|
||||
preferencesPresenter = DefaultRageshakePreferencesPresenter(
|
||||
rageshake = rageshake,
|
||||
rageshakeDataStore = rageshakeDataStore,
|
||||
)
|
||||
)
|
||||
return RootPresenter(
|
||||
crashDetectionPresenter = crashDetectionPresenter,
|
||||
rageshakeDetectionPresenter = rageshakeDetectionPresenter,
|
||||
crashDetectionPresenter = { aCrashDetectionState() },
|
||||
rageshakeDetectionPresenter = { aRageshakeDetectionState() },
|
||||
appErrorStateService = appErrorService,
|
||||
analyticsService = FakeAnalyticsService(),
|
||||
shareService = shareService,
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.analytics.api.preferences
|
||||
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
||||
interface AnalyticsPreferencesPresenter : Presenter<AnalyticsPreferencesState>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.analytics.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState
|
||||
import io.element.android.features.analytics.impl.preferences.AnalyticsPreferencesPresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
@Module
|
||||
interface AnalyticsModule {
|
||||
@Binds
|
||||
fun bindAnalyticsPreferencesPresenter(presenter: AnalyticsPreferencesPresenter): Presenter<AnalyticsPreferencesState>
|
||||
}
|
||||
|
|
@ -10,23 +10,20 @@ package io.element.android.features.analytics.impl.preferences
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.appconfig.AnalyticsConfig
|
||||
import io.element.android.features.analytics.api.AnalyticsOptInEvents
|
||||
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesPresenter
|
||||
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultAnalyticsPreferencesPresenter @Inject constructor(
|
||||
class AnalyticsPreferencesPresenter @Inject constructor(
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val buildMeta: BuildMeta,
|
||||
) : AnalyticsPreferencesPresenter {
|
||||
) : Presenter<AnalyticsPreferencesState> {
|
||||
@Composable
|
||||
override fun present(): AnalyticsPreferencesState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
|
@ -25,7 +25,7 @@ class AnalyticsPreferencesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - initial state available`() = runTest {
|
||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
||||
val presenter = AnalyticsPreferencesPresenter(
|
||||
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
|
||||
aBuildMeta()
|
||||
)
|
||||
|
|
@ -41,7 +41,7 @@ class AnalyticsPreferencesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - initial state not available`() = runTest {
|
||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
||||
val presenter = AnalyticsPreferencesPresenter(
|
||||
FakeAnalyticsService(isEnabled = false, didAskUserConsent = false),
|
||||
aBuildMeta()
|
||||
)
|
||||
|
|
@ -55,7 +55,7 @@ class AnalyticsPreferencesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - enable and disable`() = runTest {
|
||||
val presenter = DefaultAnalyticsPreferencesPresenter(
|
||||
val presenter = AnalyticsPreferencesPresenter(
|
||||
FakeAnalyticsService(isEnabled = true, didAskUserConsent = true),
|
||||
aBuildMeta()
|
||||
)
|
||||
|
|
|
|||
|
|
@ -15,18 +15,19 @@ import io.element.android.features.ftue.impl.state.DefaultFtueService
|
|||
import io.element.android.features.ftue.impl.state.FtueStep
|
||||
import io.element.android.features.lockscreen.api.LockScreenService
|
||||
import io.element.android.features.lockscreen.test.FakeLockScreenService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
|
||||
import io.element.android.libraries.matrix.api.verification.SessionVerifiedStatus
|
||||
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
|
||||
import io.element.android.libraries.permissions.api.PermissionStateProvider
|
||||
import io.element.android.libraries.permissions.impl.FakePermissionStateProvider
|
||||
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.services.toolbox.test.sdk.FakeBuildVersionSdkIntProvider
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
|
|
@ -36,8 +37,9 @@ class DefaultFtueServiceTest {
|
|||
val sessionVerificationService = FakeSessionVerificationService().apply {
|
||||
givenVerifiedStatus(SessionVerifiedStatus.Unknown)
|
||||
}
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val service = createDefaultFtueService(coroutineScope, sessionVerificationService)
|
||||
val service = createDefaultFtueService(
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
)
|
||||
|
||||
service.state.test {
|
||||
// Verification state is unknown, we don't display the flow yet
|
||||
|
|
@ -47,9 +49,6 @@ class DefaultFtueServiceTest {
|
|||
sessionVerificationService.givenVerifiedStatus(SessionVerifiedStatus.NotVerified)
|
||||
assertThat(awaitItem()).isEqualTo(FtueState.Incomplete)
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -58,10 +57,7 @@ class DefaultFtueServiceTest {
|
|||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = true)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
|
|
@ -75,9 +71,6 @@ class DefaultFtueServiceTest {
|
|||
service.updateState()
|
||||
|
||||
assertThat(service.state.value).isEqualTo(FtueState.Complete)
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -88,10 +81,7 @@ class DefaultFtueServiceTest {
|
|||
val analyticsService = FakeAnalyticsService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
|
|
@ -126,20 +116,15 @@ class DefaultFtueServiceTest {
|
|||
// Final state
|
||||
null,
|
||||
)
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if a check for a step is true, start from the next one`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val permissionStateProvider = FakePermissionStateProvider(permissionGranted = false)
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
analyticsService = analyticsService,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
|
|
@ -155,14 +140,10 @@ class DefaultFtueServiceTest {
|
|||
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(service.getNextStep(null)).isNull()
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `if version is older than 13 we don't display the notification opt in screen`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val lockScreenService = FakeLockScreenService()
|
||||
|
|
@ -170,7 +151,6 @@ class DefaultFtueServiceTest {
|
|||
val service = createDefaultFtueService(
|
||||
sdkIntVersion = Build.VERSION_CODES.M,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
coroutineScope = coroutineScope,
|
||||
analyticsService = analyticsService,
|
||||
lockScreenService = lockScreenService,
|
||||
)
|
||||
|
|
@ -182,14 +162,10 @@ class DefaultFtueServiceTest {
|
|||
|
||||
analyticsService.setDidAskUserConsent()
|
||||
assertThat(service.getNextStep(null)).isNull()
|
||||
|
||||
// Cleanup
|
||||
coroutineScope.cancel()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `reset do the expected actions S`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val resetAnalyticsLambda = lambdaRecorder<Unit> { }
|
||||
val analyticsService = FakeAnalyticsService(
|
||||
resetLambda = resetAnalyticsLambda
|
||||
|
|
@ -199,7 +175,6 @@ class DefaultFtueServiceTest {
|
|||
resetPermissionLambda = resetPermissionLambda
|
||||
)
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sdkIntVersion = Build.VERSION_CODES.S,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
analyticsService = analyticsService,
|
||||
|
|
@ -211,7 +186,6 @@ class DefaultFtueServiceTest {
|
|||
|
||||
@Test
|
||||
fun `reset do the expected actions TIRAMISU`() = runTest {
|
||||
val coroutineScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val resetLambda = lambdaRecorder<Unit> { }
|
||||
val analyticsService = FakeAnalyticsService(
|
||||
resetLambda = resetLambda
|
||||
|
|
@ -221,7 +195,6 @@ class DefaultFtueServiceTest {
|
|||
resetPermissionLambda = resetPermissionLambda
|
||||
)
|
||||
val service = createDefaultFtueService(
|
||||
coroutineScope = coroutineScope,
|
||||
sdkIntVersion = Build.VERSION_CODES.TIRAMISU,
|
||||
permissionStateProvider = permissionStateProvider,
|
||||
analyticsService = analyticsService,
|
||||
|
|
@ -232,17 +205,16 @@ class DefaultFtueServiceTest {
|
|||
.with(value("android.permission.POST_NOTIFICATIONS"))
|
||||
}
|
||||
|
||||
private fun createDefaultFtueService(
|
||||
coroutineScope: CoroutineScope,
|
||||
sessionVerificationService: FakeSessionVerificationService = FakeSessionVerificationService(),
|
||||
private fun TestScope.createDefaultFtueService(
|
||||
sessionVerificationService: SessionVerificationService = FakeSessionVerificationService(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
permissionStateProvider: FakePermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
|
||||
permissionStateProvider: PermissionStateProvider = FakePermissionStateProvider(permissionGranted = false),
|
||||
lockScreenService: LockScreenService = FakeLockScreenService(),
|
||||
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
// First version where notification permission is required
|
||||
sdkIntVersion: Int = Build.VERSION_CODES.TIRAMISU,
|
||||
) = DefaultFtueService(
|
||||
sessionCoroutineScope = coroutineScope,
|
||||
sessionCoroutineScope = backgroundScope,
|
||||
sessionVerificationService = sessionVerificationService,
|
||||
sdkVersionProvider = FakeBuildVersionSdkIntProvider(sdkIntVersion),
|
||||
analyticsService = analyticsService,
|
||||
|
|
|
|||
|
|
@ -1,16 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.leaveroom.api
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
||||
interface LeaveRoomPresenter : Presenter<LeaveRoomState> {
|
||||
@Composable
|
||||
override fun present(): LeaveRoomState
|
||||
}
|
||||
|
|
@ -57,9 +57,10 @@ fun aLeaveRoomState(
|
|||
confirmation: LeaveRoomState.Confirmation = LeaveRoomState.Confirmation.Hidden,
|
||||
progress: LeaveRoomState.Progress = LeaveRoomState.Progress.Hidden,
|
||||
error: LeaveRoomState.Error = LeaveRoomState.Error.Hidden,
|
||||
eventSink: (LeaveRoomEvent) -> Unit = {},
|
||||
) = LeaveRoomState(
|
||||
confirmation = confirmation,
|
||||
progress = progress,
|
||||
error = error,
|
||||
eventSink = {},
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -12,16 +12,14 @@ import androidx.compose.runtime.MutableState
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Dm
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.Generic
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.LastUserInRoom
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState.Confirmation.PrivateRoom
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipObserver
|
||||
|
|
@ -30,12 +28,11 @@ import kotlinx.coroutines.launch
|
|||
import timber.log.Timber
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultLeaveRoomPresenter @Inject constructor(
|
||||
class LeaveRoomPresenter @Inject constructor(
|
||||
private val client: MatrixClient,
|
||||
private val roomMembershipObserver: RoomMembershipObserver,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
) : LeaveRoomPresenter {
|
||||
) : Presenter<LeaveRoomState> {
|
||||
@Composable
|
||||
override fun present(): LeaveRoomState {
|
||||
val scope = rememberCoroutineScope()
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.leaveroom.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.leaveroom.impl.LeaveRoomPresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesTo(SessionScope::class)
|
||||
@Module
|
||||
interface LeaveRoomModule {
|
||||
@Binds
|
||||
fun bindLeaveRoomPresenter(presenter: LeaveRoomPresenter): Presenter<LeaveRoomState>
|
||||
}
|
||||
|
|
@ -27,13 +27,13 @@ import kotlinx.coroutines.test.runTest
|
|||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultLeaveRoomPresenterTest {
|
||||
class LeaveRoomPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state hides all dialogs`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter()
|
||||
val presenter = createLeaveRoomPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -46,7 +46,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show generic confirmation`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -66,7 +66,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show private room confirmation`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -86,7 +86,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show last user in room confirmation`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -106,7 +106,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show DM confirmation`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -127,7 +127,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
@Test
|
||||
fun `present - leaving a room leaves the room`() = runTest {
|
||||
val roomMembershipObserver = RoomMembershipObserver()
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -151,7 +151,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show error if leave room fails`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -175,7 +175,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - show progress indicator while leaving a room`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -199,7 +199,7 @@ class DefaultLeaveRoomPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - hide error hides the error`() = runTest {
|
||||
val presenter = createDefaultLeaveRoomPresenter(
|
||||
val presenter = createLeaveRoomPresenter(
|
||||
client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(
|
||||
roomId = A_ROOM_ID,
|
||||
|
|
@ -225,10 +225,10 @@ class DefaultLeaveRoomPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createDefaultLeaveRoomPresenter(
|
||||
private fun TestScope.createLeaveRoomPresenter(
|
||||
client: MatrixClient = FakeMatrixClient(),
|
||||
roomMembershipObserver: RoomMembershipObserver = RoomMembershipObserver(),
|
||||
): DefaultLeaveRoomPresenter = DefaultLeaveRoomPresenter(
|
||||
): LeaveRoomPresenter = LeaveRoomPresenter(
|
||||
client = client,
|
||||
roomMembershipObserver = roomMembershipObserver,
|
||||
dispatchers = testCoroutineDispatchers(false),
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
/*
|
||||
* Copyright 2022-2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.features.leaveroom.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
implementation(projects.libraries.core)
|
||||
implementation(projects.libraries.architecture)
|
||||
implementation(projects.libraries.matrix.api)
|
||||
api(projects.features.leaveroom.api)
|
||||
}
|
||||
|
|
@ -1,38 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.leaveroom.fake
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
|
||||
class FakeLeaveRoomPresenter : LeaveRoomPresenter {
|
||||
val events = mutableListOf<LeaveRoomEvent>()
|
||||
|
||||
private fun handleEvent(event: LeaveRoomEvent) {
|
||||
events += event
|
||||
}
|
||||
|
||||
private var state = LeaveRoomState(
|
||||
confirmation = LeaveRoomState.Confirmation.Hidden,
|
||||
progress = LeaveRoomState.Progress.Hidden,
|
||||
error = LeaveRoomState.Error.Hidden,
|
||||
eventSink = ::handleEvent,
|
||||
)
|
||||
set(value) {
|
||||
field = value.copy(eventSink = ::handleEvent)
|
||||
}
|
||||
|
||||
fun givenState(state: LeaveRoomState) {
|
||||
this.state = state
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): LeaveRoomState = state
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.login.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
@Module
|
||||
interface LoginModule {
|
||||
@Binds
|
||||
fun bindChangeServerPresenter(presenter: ChangeServerPresenter): Presenter<ChangeServerState>
|
||||
}
|
||||
|
|
@ -10,12 +10,12 @@ package io.element.android.features.login.impl.screens.changeaccountprovider
|
|||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.appconfig.AuthenticationConfig
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import javax.inject.Inject
|
||||
|
||||
class ChangeAccountProviderPresenter @Inject constructor(
|
||||
private val changeServerPresenter: ChangeServerPresenter,
|
||||
private val changeServerPresenter: Presenter<ChangeServerState>,
|
||||
) : Presenter<ChangeAccountProviderState> {
|
||||
@Composable
|
||||
override fun present(): ChangeAccountProviderState {
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerState
|
||||
import io.element.android.features.login.impl.resolver.HomeserverData
|
||||
import io.element.android.features.login.impl.resolver.HomeserverResolver
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
|
|
@ -27,7 +27,7 @@ import javax.inject.Inject
|
|||
|
||||
class SearchAccountProviderPresenter @Inject constructor(
|
||||
private val homeserverResolver: HomeserverResolver,
|
||||
private val changeServerPresenter: ChangeServerPresenter,
|
||||
private val changeServerPresenter: Presenter<ChangeServerState>,
|
||||
) : Presenter<SearchAccountProviderState> {
|
||||
@Composable
|
||||
override fun present(): SearchAccountProviderState {
|
||||
|
|
|
|||
|
|
@ -12,9 +12,7 @@ import app.cash.molecule.moleculeFlow
|
|||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProvider
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.features.login.impl.changeserver.aChangeServerState
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
|
|
@ -26,12 +24,8 @@ class ChangeAccountProviderPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val changeServerPresenter = ChangeServerPresenter(
|
||||
FakeMatrixAuthenticationService(),
|
||||
AccountProviderDataSource()
|
||||
)
|
||||
val presenter = ChangeAccountProviderPresenter(
|
||||
changeServerPresenter
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
|
|||
|
|
@ -11,15 +11,13 @@ import app.cash.molecule.RecompositionMode
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.login.impl.accountprovider.AccountProviderDataSource
|
||||
import io.element.android.features.login.impl.changeserver.ChangeServerPresenter
|
||||
import io.element.android.features.login.impl.changeserver.aChangeServerState
|
||||
import io.element.android.features.login.impl.resolver.HomeserverResolver
|
||||
import io.element.android.features.login.impl.resolver.network.FakeWellknownRequest
|
||||
import io.element.android.features.login.impl.resolver.network.WellKnown
|
||||
import io.element.android.features.login.impl.resolver.network.WellKnownBaseConfig
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.matrix.test.A_HOMESERVER_URL
|
||||
import io.element.android.libraries.matrix.test.auth.FakeMatrixAuthenticationService
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.test.runTest
|
||||
|
|
@ -33,13 +31,9 @@ class SearchAccountProviderPresenterTest {
|
|||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val fakeWellknownRequest = FakeWellknownRequest()
|
||||
val changeServerPresenter = ChangeServerPresenter(
|
||||
FakeMatrixAuthenticationService(),
|
||||
AccountProviderDataSource()
|
||||
)
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
changeServerPresenter
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -53,13 +47,9 @@ class SearchAccountProviderPresenterTest {
|
|||
@Test
|
||||
fun `present - enter text no result`() = runTest {
|
||||
val fakeWellknownRequest = FakeWellknownRequest()
|
||||
val changeServerPresenter = ChangeServerPresenter(
|
||||
FakeMatrixAuthenticationService(),
|
||||
AccountProviderDataSource()
|
||||
)
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
changeServerPresenter
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -77,13 +67,9 @@ class SearchAccountProviderPresenterTest {
|
|||
@Test
|
||||
fun `present - enter valid url no wellknown`() = runTest {
|
||||
val fakeWellknownRequest = FakeWellknownRequest()
|
||||
val changeServerPresenter = ChangeServerPresenter(
|
||||
FakeMatrixAuthenticationService(),
|
||||
AccountProviderDataSource()
|
||||
)
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
changeServerPresenter
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -112,13 +98,9 @@ class SearchAccountProviderPresenterTest {
|
|||
"https://test.io" to aWellKnown(),
|
||||
)
|
||||
)
|
||||
val changeServerPresenter = ChangeServerPresenter(
|
||||
FakeMatrixAuthenticationService(),
|
||||
AccountProviderDataSource()
|
||||
)
|
||||
val presenter = SearchAccountProviderPresenter(
|
||||
HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
changeServerPresenter
|
||||
homeserverResolver = HomeserverResolver(testCoroutineDispatchers(), fakeWellknownRequest),
|
||||
changeServerPresenter = { aChangeServerState() }
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.logout.api.direct
|
||||
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
||||
interface DirectLogoutPresenter : Presenter<DirectLogoutState>
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.logout.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.impl.direct.DirectLogoutPresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
|
||||
@ContributesTo(SessionScope::class)
|
||||
@Module
|
||||
interface LogoutModule {
|
||||
@Binds
|
||||
fun bindDirectLogoutPresenter(presenter: DirectLogoutPresenter): Presenter<DirectLogoutState>
|
||||
}
|
||||
|
|
@ -14,14 +14,12 @@ import androidx.compose.runtime.getValue
|
|||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutEvents
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.impl.tools.isBackingUp
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
import io.element.android.libraries.di.SessionScope
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.encryption.BackupUploadState
|
||||
import io.element.android.libraries.matrix.api.encryption.EncryptionService
|
||||
|
|
@ -29,11 +27,10 @@ import kotlinx.coroutines.CoroutineScope
|
|||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(SessionScope::class)
|
||||
class DefaultDirectLogoutPresenter @Inject constructor(
|
||||
class DirectLogoutPresenter @Inject constructor(
|
||||
private val matrixClient: MatrixClient,
|
||||
private val encryptionService: EncryptionService,
|
||||
) : DirectLogoutPresenter {
|
||||
) : Presenter<DirectLogoutState> {
|
||||
@Composable
|
||||
override fun present(): DirectLogoutState {
|
||||
val localCoroutineScope = rememberCoroutineScope()
|
||||
|
|
@ -26,13 +26,13 @@ import kotlinx.coroutines.test.runTest
|
|||
import org.junit.Rule
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultDirectLogoutPresenterTest {
|
||||
class DirectLogoutPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createDefaultDirectLogoutPresenter()
|
||||
val presenter = createDirectLogoutPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -44,7 +44,7 @@ class DefaultDirectLogoutPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - initial state - last session`() = runTest {
|
||||
val presenter = createDefaultDirectLogoutPresenter(
|
||||
val presenter = createDirectLogoutPresenter(
|
||||
encryptionService = FakeEncryptionService().apply {
|
||||
emitIsLastDevice(true)
|
||||
}
|
||||
|
|
@ -66,7 +66,7 @@ class DefaultDirectLogoutPresenterTest {
|
|||
emit(BackupUploadState.Waiting)
|
||||
}
|
||||
)
|
||||
val presenter = createDefaultDirectLogoutPresenter(
|
||||
val presenter = createDirectLogoutPresenter(
|
||||
encryptionService = encryptionService
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -81,7 +81,7 @@ class DefaultDirectLogoutPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - logout then cancel`() = runTest {
|
||||
val presenter = createDefaultDirectLogoutPresenter()
|
||||
val presenter = createDirectLogoutPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -97,7 +97,7 @@ class DefaultDirectLogoutPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - logout then confirm`() = runTest {
|
||||
val presenter = createDefaultDirectLogoutPresenter()
|
||||
val presenter = createDirectLogoutPresenter()
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -120,7 +120,7 @@ class DefaultDirectLogoutPresenterTest {
|
|||
throw A_THROWABLE
|
||||
}
|
||||
}
|
||||
val presenter = createDefaultDirectLogoutPresenter(
|
||||
val presenter = createDirectLogoutPresenter(
|
||||
matrixClient,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -152,7 +152,7 @@ class DefaultDirectLogoutPresenterTest {
|
|||
}
|
||||
}
|
||||
}
|
||||
val presenter = createDefaultDirectLogoutPresenter(
|
||||
val presenter = createDirectLogoutPresenter(
|
||||
matrixClient,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -179,10 +179,10 @@ class DefaultDirectLogoutPresenterTest {
|
|||
return awaitItem()
|
||||
}
|
||||
|
||||
private fun createDefaultDirectLogoutPresenter(
|
||||
private fun createDirectLogoutPresenter(
|
||||
matrixClient: MatrixClient = FakeMatrixClient(),
|
||||
encryptionService: EncryptionService = FakeEncryptionService(),
|
||||
): DefaultDirectLogoutPresenter = DefaultDirectLogoutPresenter(
|
||||
): DirectLogoutPresenter = DirectLogoutPresenter(
|
||||
matrixClient = matrixClient,
|
||||
encryptionService = encryptionService,
|
||||
)
|
||||
|
|
@ -32,22 +32,21 @@ import io.element.android.features.messages.impl.actionlist.ActionListPresenter
|
|||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.timeline.TimelineEvents
|
||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||
import io.element.android.features.messages.impl.timeline.TimelineState
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
|
||||
import io.element.android.features.messages.impl.timeline.model.TimelineItem
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemStateContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextBasedContent
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.libraries.androidutils.clipboard.ClipboardHelper
|
||||
|
|
@ -88,14 +87,14 @@ import timber.log.Timber
|
|||
class MessagesPresenter @AssistedInject constructor(
|
||||
@Assisted private val navigator: MessagesNavigator,
|
||||
private val room: MatrixRoom,
|
||||
private val composerPresenter: MessageComposerPresenter,
|
||||
private val voiceMessageComposerPresenter: VoiceMessageComposerPresenter,
|
||||
private val composerPresenter: Presenter<MessageComposerState>,
|
||||
private val voiceMessageComposerPresenter: Presenter<VoiceMessageComposerState>,
|
||||
timelinePresenterFactory: TimelinePresenter.Factory,
|
||||
private val timelineProtectionPresenter: Presenter<TimelineProtectionState>,
|
||||
private val actionListPresenterFactory: ActionListPresenter.Factory,
|
||||
private val customReactionPresenter: CustomReactionPresenter,
|
||||
private val reactionSummaryPresenter: ReactionSummaryPresenter,
|
||||
private val readReceiptBottomSheetPresenter: ReadReceiptBottomSheetPresenter,
|
||||
private val customReactionPresenter: Presenter<CustomReactionState>,
|
||||
private val reactionSummaryPresenter: Presenter<ReactionSummaryState>,
|
||||
private val readReceiptBottomSheetPresenter: Presenter<ReadReceiptBottomSheetState>,
|
||||
private val pinnedMessagesBannerPresenter: Presenter<PinnedMessagesBannerState>,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
|
|
|
|||
|
|
@ -12,12 +12,22 @@ import dagger.Binds
|
|||
import dagger.Module
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailurePresenter
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.ResolveVerifiedUserSendFailureState
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerPresenter
|
||||
import io.element.android.features.messages.impl.pinned.banner.PinnedMessagesBannerState
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionState
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryState
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetState
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionPresenter
|
||||
import io.element.android.features.messages.impl.timeline.protection.TimelineProtectionState
|
||||
import io.element.android.features.messages.impl.typing.TypingNotificationPresenter
|
||||
import io.element.android.features.messages.impl.typing.TypingNotificationState
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
|
||||
|
|
@ -35,4 +45,19 @@ interface MessagesModule {
|
|||
|
||||
@Binds
|
||||
fun bindTimelineProtectionPresenter(presenter: TimelineProtectionPresenter): Presenter<TimelineProtectionState>
|
||||
|
||||
@Binds
|
||||
fun bindMessageComposerPresenter(presenter: MessageComposerPresenter): Presenter<MessageComposerState>
|
||||
|
||||
@Binds
|
||||
fun bindVoiceMessageComposerPresenter(presenter: VoiceMessageComposerPresenter): Presenter<VoiceMessageComposerState>
|
||||
|
||||
@Binds
|
||||
fun bindCustomReactionPresenter(presenter: CustomReactionPresenter): Presenter<CustomReactionState>
|
||||
|
||||
@Binds
|
||||
fun bindReactionSummaryPresenter(presenter: ReactionSummaryPresenter): Presenter<ReactionSummaryState>
|
||||
|
||||
@Binds
|
||||
fun bindReadReceiptBottomSheetPresenter(presenter: ReadReceiptBottomSheetPresenter): Presenter<ReadReceiptBottomSheetState>
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,6 +33,7 @@ fun aMessageComposerState(
|
|||
canCreatePoll: Boolean = true,
|
||||
attachmentsState: AttachmentsState = AttachmentsState.None,
|
||||
suggestions: ImmutableList<ResolvedSuggestion> = persistentListOf(),
|
||||
eventSink: (MessageComposerEvents) -> Unit = {},
|
||||
) = MessageComposerState(
|
||||
textEditorState = textEditorState,
|
||||
isFullScreen = isFullScreen,
|
||||
|
|
@ -44,5 +45,5 @@ fun aMessageComposerState(
|
|||
attachmentsState = attachmentsState,
|
||||
suggestions = suggestions,
|
||||
resolveMentionDisplay = { _, _ -> TextDisplay.Plain },
|
||||
eventSink = {},
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,33 +7,23 @@
|
|||
|
||||
package io.element.android.features.messages.impl
|
||||
|
||||
import android.net.Uri
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import im.vector.app.features.analytics.plan.PinUnpinAction
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListEvents
|
||||
import io.element.android.features.messages.impl.actionlist.ActionListState
|
||||
import io.element.android.features.messages.impl.actionlist.FakeActionListPresenter
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemAction
|
||||
import io.element.android.features.messages.impl.crypto.sendfailure.resolve.aResolveVerifiedUserSendFailureState
|
||||
import io.element.android.features.messages.impl.draft.FakeComposerDraftService
|
||||
import io.element.android.features.messages.impl.fixtures.aMessageEvent
|
||||
import io.element.android.features.messages.impl.fixtures.aTimelineItemsFactoryCreator
|
||||
import io.element.android.features.messages.impl.messagecomposer.DefaultMessageComposerContext
|
||||
import io.element.android.features.messages.impl.messagecomposer.FakeRoomAliasSuggestionsDataSource
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.messagecomposer.TestRichTextEditorStateFactory
|
||||
import io.element.android.features.messages.impl.messagecomposer.suggestions.SuggestionsProcessor
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerEvents
|
||||
import io.element.android.features.messages.impl.messagecomposer.MessageComposerState
|
||||
import io.element.android.features.messages.impl.messagecomposer.aMessageComposerState
|
||||
import io.element.android.features.messages.impl.pinned.banner.aLoadedPinnedMessagesBannerState
|
||||
import io.element.android.features.messages.impl.timeline.TimelineController
|
||||
import io.element.android.features.messages.impl.timeline.TimelineItemIndexer
|
||||
import io.element.android.features.messages.impl.timeline.TimelinePresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.CustomReactionPresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.customreaction.FakeEmojibaseProvider
|
||||
import io.element.android.features.messages.impl.timeline.components.reactionsummary.ReactionSummaryPresenter
|
||||
import io.element.android.features.messages.impl.timeline.components.receipt.bottomsheet.ReadReceiptBottomSheetPresenter
|
||||
import io.element.android.features.messages.impl.timeline.createTimelinePresenter
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemFileContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemImageContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.TimelineItemTextContent
|
||||
|
|
@ -41,25 +31,19 @@ import io.element.android.features.messages.impl.timeline.model.event.TimelineIt
|
|||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemPollContent
|
||||
import io.element.android.features.messages.impl.timeline.model.event.aTimelineItemTextContent
|
||||
import io.element.android.features.messages.impl.timeline.protection.aTimelineProtectionState
|
||||
import io.element.android.features.messages.impl.typing.aTypingNotificationState
|
||||
import io.element.android.features.messages.impl.utils.FakeTextPillificationHelper
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPlayer
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.VoiceMessageComposerPresenter
|
||||
import io.element.android.features.messages.impl.voicemessages.timeline.FakeRedactedVoiceMessageManager
|
||||
import io.element.android.features.messages.test.FakeMessageComposerContext
|
||||
import io.element.android.features.messages.impl.voicemessages.composer.aVoiceMessageComposerState
|
||||
import io.element.android.features.messages.test.timeline.FakeHtmlConverterProvider
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
import io.element.android.features.poll.api.actions.EndPollAction
|
||||
import io.element.android.features.poll.test.actions.FakeEndPollAction
|
||||
import io.element.android.features.poll.test.actions.FakeSendPollResponseAction
|
||||
import io.element.android.libraries.androidutils.clipboard.FakeClipboardHelper
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.mimetype.MimeTypes
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarData
|
||||
import io.element.android.libraries.designsystem.components.avatar.AvatarSize
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.matrix.api.core.EventId
|
||||
import io.element.android.libraries.matrix.api.core.TransactionId
|
||||
|
|
@ -72,33 +56,24 @@ import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
|||
import io.element.android.libraries.matrix.api.room.MessageEventType
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
|
||||
import io.element.android.libraries.matrix.test.AN_EVENT_ID
|
||||
import io.element.android.libraries.matrix.test.A_ROOM_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID
|
||||
import io.element.android.libraries.matrix.test.A_SESSION_ID_2
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
import io.element.android.libraries.matrix.test.A_UNIQUE_ID
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkBuilder
|
||||
import io.element.android.libraries.matrix.test.permalink.FakePermalinkParser
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.libraries.matrix.test.room.aRoomMember
|
||||
import io.element.android.libraries.matrix.test.timeline.FakeTimeline
|
||||
import io.element.android.libraries.matrix.ui.messages.RoomMemberProfilesCache
|
||||
import io.element.android.libraries.matrix.ui.messages.reply.InReplyToDetails
|
||||
import io.element.android.libraries.mediapickers.test.FakePickerProvider
|
||||
import io.element.android.libraries.mediaplayer.test.FakeMediaPlayer
|
||||
import io.element.android.libraries.mediaupload.api.MediaSender
|
||||
import io.element.android.libraries.mediaupload.test.FakeMediaPreProcessor
|
||||
import io.element.android.libraries.mediaviewer.test.FakeLocalMediaFactory
|
||||
import io.element.android.libraries.permissions.api.PermissionsPresenter
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenter
|
||||
import io.element.android.libraries.permissions.test.FakePermissionsPresenterFactory
|
||||
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
|
||||
import io.element.android.libraries.textcomposer.mentions.MentionSpanProvider
|
||||
import io.element.android.libraries.textcomposer.model.MarkdownTextEditorState
|
||||
import io.element.android.libraries.textcomposer.model.MessageComposerMode
|
||||
import io.element.android.libraries.voicerecorder.test.FakeVoiceRecorder
|
||||
import io.element.android.libraries.textcomposer.model.TextEditorState
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
import io.element.android.tests.testutils.consumeItemsUntilTimeout
|
||||
|
|
@ -107,7 +82,6 @@ import io.element.android.tests.testutils.lambda.lambdaError
|
|||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import io.mockk.mockk
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.delay
|
||||
|
|
@ -123,8 +97,6 @@ class MessagesPresenterTest {
|
|||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val mockMediaUrl: Uri = mockk("localMediaUri")
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val presenter = createMessagesPresenter()
|
||||
|
|
@ -212,15 +184,13 @@ class MessagesPresenterTest {
|
|||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
||||
// No crashes when sending a reaction failed
|
||||
timeline.apply { toggleReactionLambda = toggleReactionFailure }
|
||||
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
|
||||
initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
||||
assert(toggleReactionSuccess)
|
||||
.isCalledOnce()
|
||||
.with(value("👍"), value(A_UNIQUE_ID))
|
||||
// No crashes when sending a reaction failed
|
||||
timeline.apply { toggleReactionLambda = toggleReactionFailure }
|
||||
initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
||||
assert(toggleReactionFailure)
|
||||
.isCalledOnce()
|
||||
.with(value("👍"), value(A_UNIQUE_ID))
|
||||
|
|
@ -248,15 +218,16 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
||||
initialState.eventSink.invoke(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
||||
initialState.eventSink(MessagesEvents.ToggleReaction("👍", A_UNIQUE_ID))
|
||||
assert(toggleReactionSuccess)
|
||||
.isCalledExactly(2)
|
||||
.withSequence(
|
||||
listOf(value("👍"), value(A_UNIQUE_ID)),
|
||||
listOf(value("👍"), value(A_UNIQUE_ID)),
|
||||
)
|
||||
skipItems(1)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -267,9 +238,8 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent()))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Forward, aMessageEvent()))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
assertThat(navigator.onForwardEventClickedCount).isEqualTo(1)
|
||||
}
|
||||
|
|
@ -283,9 +253,9 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Copy, event))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Copy, event))
|
||||
skipItems(2)
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo((event.content as TimelineItemTextContent).body)
|
||||
}
|
||||
}
|
||||
|
|
@ -310,24 +280,33 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.CopyLink, event))
|
||||
skipItems(2)
|
||||
assertThat(clipboardHelper.clipboardContents).isEqualTo("a link")
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action reply`() = runTest {
|
||||
val presenter = createMessagesPresenter()
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent()))
|
||||
awaitItem()
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.Reply(
|
||||
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
||||
hideImage = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -337,21 +316,22 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
|
||||
assertThat(initialState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
// Otherwise we would have some extra items here
|
||||
ensureAllEventsConsumed()
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, aMessageEvent(eventId = null)))
|
||||
skipItems(1)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action reply to an image media message`() = runTest {
|
||||
val presenter = createMessagesPresenter()
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemImageContent(
|
||||
body = "image.jpg",
|
||||
|
|
@ -368,22 +348,29 @@ class MessagesPresenterTest {
|
|||
formattedFileSize = "4MB"
|
||||
)
|
||||
)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||
assertThat(replyMode.replyToDetails).isInstanceOf(InReplyToDetails.Loading::class.java)
|
||||
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
awaitItem()
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.Reply(
|
||||
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
||||
hideImage = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action reply to a video media message`() = runTest {
|
||||
val presenter = createMessagesPresenter()
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemVideoContent(
|
||||
body = "video.mp4",
|
||||
|
|
@ -401,22 +388,29 @@ class MessagesPresenterTest {
|
|||
formattedFileSize = "50MB"
|
||||
)
|
||||
)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||
assertThat(replyMode.replyToDetails).isInstanceOf(InReplyToDetails.Loading::class.java)
|
||||
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
awaitItem()
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.Reply(
|
||||
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
||||
hideImage = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action reply to a file media message`() = runTest {
|
||||
val presenter = createMessagesPresenter()
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
val mediaMessage = aMessageEvent(
|
||||
content = TimelineItemFileContent(
|
||||
body = "file.pdf",
|
||||
|
|
@ -427,26 +421,40 @@ class MessagesPresenterTest {
|
|||
fileExtension = "pdf",
|
||||
)
|
||||
)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||
assertThat(replyMode.replyToDetails).isInstanceOf(InReplyToDetails.Loading::class.java)
|
||||
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, mediaMessage))
|
||||
awaitItem()
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.Reply(
|
||||
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
||||
hideImage = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle action edit`() = runTest {
|
||||
val presenter = createMessagesPresenter()
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Edit::class.java)
|
||||
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent()))
|
||||
awaitItem()
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.Edit(
|
||||
eventId = AN_EVENT_ID,
|
||||
transactionId = null,
|
||||
content = (aMessageEvent().content as TimelineItemTextContent).body
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -457,8 +465,9 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent(content = aTimelineItemPollContent())))
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Edit, aMessageEvent(content = aTimelineItemPollContent())))
|
||||
awaitItem()
|
||||
assertThat(navigator.onEditPollClickedCount).isEqualTo(1)
|
||||
}
|
||||
}
|
||||
|
|
@ -470,9 +479,9 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
endPollAction.verifyExecutionCount(0)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.EndPoll, aMessageEvent(content = aTimelineItemPollContent())))
|
||||
delay(1)
|
||||
endPollAction.verifyExecutionCount(1)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
|
|
@ -496,16 +505,17 @@ class MessagesPresenterTest {
|
|||
|
||||
val redactEventLambda = lambdaRecorder { _: EventId?, _: TransactionId?, _: String? -> Result.success(Unit) }
|
||||
liveTimeline.redactEventLambda = redactEventLambda
|
||||
|
||||
val presenter = createMessagesPresenter(matrixRoom = matrixRoom, coroutineDispatchers = coroutineDispatchers)
|
||||
val presenter = createMessagesPresenter(
|
||||
matrixRoom = matrixRoom,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
val messageEvent = aMessageEvent()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Redact, messageEvent))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Redact, messageEvent))
|
||||
awaitItem()
|
||||
assert(redactEventLambda)
|
||||
.isCalledOnce()
|
||||
.with(value(messageEvent.eventId), value(messageEvent.transactionId), value(null))
|
||||
|
|
@ -519,9 +529,8 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent()))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ReportContent, aMessageEvent()))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
assertThat(navigator.onReportContentClickedCount).isEqualTo(1)
|
||||
}
|
||||
|
|
@ -533,9 +542,8 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.Dismiss)
|
||||
initialState.eventSink(MessagesEvents.Dismiss)
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
}
|
||||
}
|
||||
|
|
@ -547,9 +555,8 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.ViewSource, aMessageEvent()))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.ViewSource, aMessageEvent()))
|
||||
assertThat(awaitItem().actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
assertThat(navigator.onShowEventDebugInfoClickedCount).isEqualTo(1)
|
||||
}
|
||||
|
|
@ -572,21 +579,18 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
skipItems(1)
|
||||
val initialState = awaitItem()
|
||||
// Initially the composer doesn't have focus, so we don't show the alert
|
||||
assertThat(initialState.showReinvitePrompt).isFalse()
|
||||
// When the input field is focused we show the alert
|
||||
initialState.composerState.textEditorState.requestFocus()
|
||||
val focusedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { state ->
|
||||
state.showReinvitePrompt
|
||||
}.last()
|
||||
(initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true
|
||||
skipItems(1)
|
||||
val focusedState = awaitItem()
|
||||
assertThat(focusedState.showReinvitePrompt).isTrue()
|
||||
// If it's dismissed then we stop showing the alert
|
||||
initialState.eventSink(MessagesEvents.InviteDialogDismissed(InviteDialogAction.Cancel))
|
||||
val dismissedState = consumeItemsUntilPredicate(timeout = 250.milliseconds) { state ->
|
||||
!state.showReinvitePrompt
|
||||
}.last()
|
||||
skipItems(1)
|
||||
val dismissedState = awaitItem()
|
||||
assertThat(dismissedState.showReinvitePrompt).isFalse()
|
||||
}
|
||||
}
|
||||
|
|
@ -608,9 +612,9 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showReinvitePrompt).isFalse()
|
||||
initialState.composerState.textEditorState.requestFocus()
|
||||
(initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true
|
||||
val focusedState = awaitItem()
|
||||
assertThat(focusedState.showReinvitePrompt).isFalse()
|
||||
}
|
||||
|
|
@ -633,9 +637,9 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.showReinvitePrompt).isFalse()
|
||||
initialState.composerState.textEditorState.requestFocus()
|
||||
(initialState.composerState.textEditorState as TextEditorState.Markdown).state.hasFocus = true
|
||||
val focusedState = awaitItem()
|
||||
assertThat(focusedState.showReinvitePrompt).isFalse()
|
||||
}
|
||||
|
|
@ -799,7 +803,8 @@ class MessagesPresenterTest {
|
|||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val state = awaitFirstItem()
|
||||
skipItems(1)
|
||||
val state = awaitItem()
|
||||
assertThat(state.userEventPermissions.canSendMessage).isTrue()
|
||||
}
|
||||
}
|
||||
|
|
@ -826,9 +831,7 @@ class MessagesPresenterTest {
|
|||
}.test {
|
||||
// Default value
|
||||
assertThat(awaitItem().userEventPermissions.canSendMessage).isTrue()
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().userEventPermissions.canSendMessage).isFalse()
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -876,21 +879,27 @@ class MessagesPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - handle action reply to a poll`() = runTest {
|
||||
val presenter = createMessagesPresenter()
|
||||
val composerRecorder = EventsRecorder<MessageComposerEvents>()
|
||||
val presenter = createMessagesPresenter(
|
||||
messageComposerPresenter = { aMessageComposerState(eventSink = composerRecorder) },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
val poll = aMessageEvent(
|
||||
content = aTimelineItemPollContent()
|
||||
)
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll))
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.composerState.mode).isInstanceOf(MessageComposerMode.Reply::class.java)
|
||||
val replyMode = finalState.composerState.mode as MessageComposerMode.Reply
|
||||
|
||||
assertThat(replyMode.replyToDetails).isInstanceOf(InReplyToDetails.Loading::class.java)
|
||||
assertThat(finalState.actionListState.target).isEqualTo(ActionListState.Target.None)
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Reply, poll))
|
||||
skipItems(1)
|
||||
composerRecorder.assertSingle(
|
||||
MessageComposerEvents.SetMode(
|
||||
composerMode = MessageComposerMode.Reply(
|
||||
replyToDetails = InReplyToDetails.Loading(AN_EVENT_ID),
|
||||
hideImage = false,
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -916,15 +925,16 @@ class MessagesPresenterTest {
|
|||
val messageEvent = aMessageEvent(
|
||||
content = aTimelineItemTextContent()
|
||||
)
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
|
||||
timeline.pinEventLambda = successPinEventLambda
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
||||
assert(successPinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
||||
|
||||
timeline.pinEventLambda = failurePinEventLambda
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Pin, messageEvent))
|
||||
assert(failurePinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().snackbarMessage).isNotNull()
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
PinUnpinAction(kind = PinUnpinAction.Kind.Pin, from = PinUnpinAction.From.Timeline),
|
||||
|
|
@ -955,15 +965,16 @@ class MessagesPresenterTest {
|
|||
val messageEvent = aMessageEvent(
|
||||
content = aTimelineItemTextContent()
|
||||
)
|
||||
val initialState = awaitFirstItem()
|
||||
val initialState = awaitItem()
|
||||
|
||||
timeline.unpinEventLambda = successUnpinEventLambda
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
||||
assert(successUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
||||
|
||||
timeline.unpinEventLambda = failureUnpinEventLambda
|
||||
initialState.eventSink.invoke(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
||||
initialState.eventSink(MessagesEvents.HandleAction(TimelineItemAction.Unpin, messageEvent))
|
||||
assert(failureUnpinEventLambda).isCalledOnce().with(value(messageEvent.eventId))
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().snackbarMessage).isNotNull()
|
||||
assertThat(analyticsService.capturedEvents).containsExactly(
|
||||
PinUnpinAction(kind = PinUnpinAction.Kind.Unpin, from = PinUnpinAction.From.Timeline),
|
||||
|
|
@ -972,12 +983,6 @@ class MessagesPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
||||
// Skip 2 item if Mentions feature is enabled, else 1
|
||||
skipItems(if (FeatureFlags.Mentions.defaultValue(aBuildMeta())) 2 else 1)
|
||||
return awaitItem()
|
||||
}
|
||||
|
||||
private fun TestScope.createMessagesPresenter(
|
||||
coroutineDispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
matrixRoom: MatrixRoom = FakeMatrixRoom(
|
||||
|
|
@ -993,83 +998,34 @@ class MessagesPresenterTest {
|
|||
navigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||
clipboardHelper: FakeClipboardHelper = FakeClipboardHelper(),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
permissionsPresenter: PermissionsPresenter = FakePermissionsPresenter(),
|
||||
endPollAction: EndPollAction = FakeEndPollAction(),
|
||||
permalinkParser: PermalinkParser = FakePermalinkParser(),
|
||||
messageComposerPresenter: Presenter<MessageComposerState> = Presenter {
|
||||
aMessageComposerState(
|
||||
// Use TextEditorState.Markdown, so that we can request focus manually.
|
||||
textEditorState = TextEditorState.Markdown(MarkdownTextEditorState(initialText = "", initialFocus = false))
|
||||
)
|
||||
},
|
||||
actionListEventSink: (ActionListEvents) -> Unit = {},
|
||||
): MessagesPresenter {
|
||||
val mediaSender = MediaSender(FakeMediaPreProcessor(), matrixRoom)
|
||||
val permissionsPresenterFactory = FakePermissionsPresenterFactory(permissionsPresenter)
|
||||
val sessionPreferencesStore = InMemorySessionPreferencesStore()
|
||||
val mentionSpanProvider = MentionSpanProvider(FakePermalinkParser())
|
||||
val messageComposerPresenter = MessageComposerPresenter(
|
||||
appCoroutineScope = this,
|
||||
room = matrixRoom,
|
||||
mediaPickerProvider = FakePickerProvider(),
|
||||
featureFlagService = FakeFeatureFlagService(mapOf(FeatureFlags.NotificationSettings.key to true)),
|
||||
sessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
localMediaFactory = FakeLocalMediaFactory(mockMediaUrl),
|
||||
mediaSender = mediaSender,
|
||||
snackbarDispatcher = SnackbarDispatcher(),
|
||||
analyticsService = analyticsService,
|
||||
messageComposerContext = DefaultMessageComposerContext(),
|
||||
richTextEditorStateFactory = TestRichTextEditorStateFactory(),
|
||||
roomAliasSuggestionsDataSource = FakeRoomAliasSuggestionsDataSource(),
|
||||
permissionsPresenterFactory = permissionsPresenterFactory,
|
||||
permalinkParser = FakePermalinkParser(),
|
||||
permalinkBuilder = FakePermalinkBuilder(),
|
||||
timelineController = TimelineController(matrixRoom),
|
||||
draftService = FakeComposerDraftService(),
|
||||
mentionSpanProvider = mentionSpanProvider,
|
||||
pillificationHelper = FakeTextPillificationHelper(),
|
||||
roomMemberProfilesCache = RoomMemberProfilesCache(),
|
||||
suggestionsProcessor = SuggestionsProcessor(),
|
||||
).apply {
|
||||
showTextFormatting = true
|
||||
isTesting = true
|
||||
}
|
||||
val voiceMessageComposerPresenter = VoiceMessageComposerPresenter(
|
||||
this,
|
||||
FakeVoiceRecorder(),
|
||||
analyticsService,
|
||||
mediaSender,
|
||||
player = VoiceMessageComposerPlayer(FakeMediaPlayer(), this),
|
||||
messageComposerContext = FakeMessageComposerContext(),
|
||||
permissionsPresenterFactory,
|
||||
)
|
||||
val timelinePresenter = TimelinePresenter(
|
||||
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
||||
room = matrixRoom,
|
||||
dispatchers = coroutineDispatchers,
|
||||
appScope = this,
|
||||
navigator = navigator,
|
||||
redactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
||||
endPollAction = endPollAction,
|
||||
sendPollResponseAction = FakeSendPollResponseAction(),
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
timelineItemIndexer = TimelineItemIndexer(),
|
||||
timelineController = TimelineController(matrixRoom),
|
||||
resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() },
|
||||
typingNotificationPresenter = { aTypingNotificationState() },
|
||||
)
|
||||
val timelinePresenterFactory = object : TimelinePresenter.Factory {
|
||||
override fun create(navigator: MessagesNavigator): TimelinePresenter {
|
||||
return timelinePresenter
|
||||
return createTimelinePresenter(
|
||||
endPollAction = endPollAction,
|
||||
)
|
||||
}
|
||||
}
|
||||
val featureFlagService = FakeFeatureFlagService()
|
||||
val readReceiptBottomSheetPresenter = ReadReceiptBottomSheetPresenter()
|
||||
val customReactionPresenter = CustomReactionPresenter(emojibaseProvider = FakeEmojibaseProvider())
|
||||
val reactionSummaryPresenter = ReactionSummaryPresenter(room = matrixRoom)
|
||||
return MessagesPresenter(
|
||||
room = matrixRoom,
|
||||
composerPresenter = messageComposerPresenter,
|
||||
voiceMessageComposerPresenter = voiceMessageComposerPresenter,
|
||||
voiceMessageComposerPresenter = { aVoiceMessageComposerState() },
|
||||
timelinePresenterFactory = timelinePresenterFactory,
|
||||
timelineProtectionPresenter = { aTimelineProtectionState() },
|
||||
actionListPresenterFactory = FakeActionListPresenter.Factory,
|
||||
customReactionPresenter = customReactionPresenter,
|
||||
reactionSummaryPresenter = reactionSummaryPresenter,
|
||||
readReceiptBottomSheetPresenter = readReceiptBottomSheetPresenter,
|
||||
actionListPresenterFactory = FakeActionListPresenter.Factory(actionListEventSink),
|
||||
customReactionPresenter = { aCustomReactionState() },
|
||||
reactionSummaryPresenter = { aReactionSummaryState() },
|
||||
readReceiptBottomSheetPresenter = { aReadReceiptBottomSheetState() },
|
||||
pinnedMessagesBannerPresenter = { aLoadedPinnedMessagesBannerState() },
|
||||
networkMonitor = FakeNetworkMonitor(),
|
||||
snackbarDispatcher = SnackbarDispatcher(),
|
||||
|
|
|
|||
|
|
@ -10,15 +10,15 @@ package io.element.android.features.messages.impl.actionlist
|
|||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.features.messages.impl.actionlist.model.TimelineItemActionPostProcessor
|
||||
|
||||
class FakeActionListPresenter : ActionListPresenter {
|
||||
object Factory : ActionListPresenter.Factory {
|
||||
class FakeActionListPresenter(private val eventSink: (ActionListEvents) -> Unit = {}) : ActionListPresenter {
|
||||
class Factory(private val eventSink: (ActionListEvents) -> Unit = {}) : ActionListPresenter.Factory {
|
||||
override fun create(postProcessor: TimelineItemActionPostProcessor): ActionListPresenter {
|
||||
return FakeActionListPresenter()
|
||||
return FakeActionListPresenter(eventSink)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): ActionListState {
|
||||
return anActionListState()
|
||||
return anActionListState(eventSink = eventSink)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -312,7 +312,7 @@ class PinnedMessagesListPresenterTest {
|
|||
timelineProvider = timelineProvider,
|
||||
timelineProtectionPresenter = { aTimelineProtectionState() },
|
||||
snackbarDispatcher = SnackbarDispatcher(),
|
||||
actionListPresenterFactory = FakeActionListPresenter.Factory,
|
||||
actionListPresenterFactory = FakeActionListPresenter.Factory(),
|
||||
analyticsService = analyticsService,
|
||||
appCoroutineScope = this,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -656,34 +656,34 @@ import kotlin.time.Duration.Companion.seconds
|
|||
private suspend fun <T> ReceiveTurbine<T>.awaitFirstItem(): T {
|
||||
return awaitItem()
|
||||
}
|
||||
|
||||
private fun TestScope.createTimelinePresenter(
|
||||
timeline: Timeline = FakeTimeline(),
|
||||
room: FakeMatrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) }
|
||||
),
|
||||
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
||||
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||
endPollAction: EndPollAction = FakeEndPollAction(),
|
||||
sendPollResponseAction: SendPollResponseAction = FakeSendPollResponseAction(),
|
||||
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
|
||||
): TimelinePresenter {
|
||||
return TimelinePresenter(
|
||||
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
||||
room = room,
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
appScope = this,
|
||||
navigator = messagesNavigator,
|
||||
redactedVoiceMessageManager = redactedVoiceMessageManager,
|
||||
endPollAction = endPollAction,
|
||||
sendPollResponseAction = sendPollResponseAction,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
timelineItemIndexer = timelineItemIndexer,
|
||||
timelineController = TimelineController(room),
|
||||
resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() },
|
||||
typingNotificationPresenter = { aTypingNotificationState() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
internal fun TestScope.createTimelinePresenter(
|
||||
timeline: Timeline = FakeTimeline(),
|
||||
room: FakeMatrixRoom = FakeMatrixRoom(
|
||||
liveTimeline = timeline,
|
||||
canUserSendMessageResult = { _, _ -> Result.success(true) }
|
||||
),
|
||||
redactedVoiceMessageManager: RedactedVoiceMessageManager = FakeRedactedVoiceMessageManager(),
|
||||
messagesNavigator: FakeMessagesNavigator = FakeMessagesNavigator(),
|
||||
endPollAction: EndPollAction = FakeEndPollAction(),
|
||||
sendPollResponseAction: SendPollResponseAction = FakeSendPollResponseAction(),
|
||||
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
timelineItemIndexer: TimelineItemIndexer = TimelineItemIndexer(),
|
||||
): TimelinePresenter {
|
||||
return TimelinePresenter(
|
||||
timelineItemsFactoryCreator = aTimelineItemsFactoryCreator(),
|
||||
room = room,
|
||||
dispatchers = testCoroutineDispatchers(),
|
||||
appScope = this,
|
||||
navigator = messagesNavigator,
|
||||
redactedVoiceMessageManager = redactedVoiceMessageManager,
|
||||
endPollAction = endPollAction,
|
||||
sendPollResponseAction = sendPollResponseAction,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
timelineItemIndexer = timelineItemIndexer,
|
||||
timelineController = TimelineController(room),
|
||||
resolveVerifiedUserSendFailurePresenter = { aResolveVerifiedUserSendFailureState() },
|
||||
typingNotificationPresenter = { aTypingNotificationState() },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -83,7 +83,6 @@ dependencies {
|
|||
testImplementation(projects.features.logout.test)
|
||||
testImplementation(projects.libraries.indicator.impl)
|
||||
testImplementation(projects.libraries.pushproviders.test)
|
||||
testImplementation(projects.libraries.fullscreenintent.test)
|
||||
testImplementation(projects.features.logout.impl)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(projects.services.toolbox.test)
|
||||
|
|
|
|||
|
|
@ -8,19 +8,19 @@
|
|||
package io.element.android.features.preferences.impl.analytics
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesPresenter
|
||||
import io.element.android.features.analytics.api.preferences.AnalyticsPreferencesState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import javax.inject.Inject
|
||||
|
||||
class AnalyticsSettingsPresenter @Inject constructor(
|
||||
private val analyticsPresenter: AnalyticsPreferencesPresenter,
|
||||
private val analyticsPreferencesPresenter: Presenter<AnalyticsPreferencesState>,
|
||||
) : Presenter<AnalyticsSettingsState> {
|
||||
@Composable
|
||||
override fun present(): AnalyticsSettingsState {
|
||||
val analyticsState = analyticsPresenter.present()
|
||||
val analyticsPreferencesState = analyticsPreferencesPresenter.present()
|
||||
|
||||
return AnalyticsSettingsState(
|
||||
analyticsState = analyticsState,
|
||||
analyticsPreferencesState = analyticsPreferencesState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -11,5 +11,5 @@ import io.element.android.features.analytics.api.preferences.AnalyticsPreference
|
|||
|
||||
// Do not use default value, so no member get forgotten in the presenters.
|
||||
data class AnalyticsSettingsState(
|
||||
val analyticsState: AnalyticsPreferencesState,
|
||||
val analyticsPreferencesState: AnalyticsPreferencesState,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -18,5 +18,5 @@ open class AnalyticsSettingsStateProvider : PreviewParameterProvider<AnalyticsSe
|
|||
}
|
||||
|
||||
fun aAnalyticsSettingsState() = AnalyticsSettingsState(
|
||||
analyticsState = aAnalyticsPreferencesState(),
|
||||
analyticsPreferencesState = aAnalyticsPreferencesState(),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -29,7 +29,7 @@ fun AnalyticsSettingsView(
|
|||
title = stringResource(id = CommonStrings.common_analytics)
|
||||
) {
|
||||
AnalyticsPreferencesView(
|
||||
state = state.analyticsState,
|
||||
state = state.analyticsPreferencesState,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import io.element.android.appconfig.ElementCallConfig
|
|||
import io.element.android.features.logout.api.LogoutUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.ClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.ComputeCacheSizeUseCase
|
||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
|
||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runCatchingUpdatingState
|
||||
|
|
@ -44,7 +44,7 @@ class DeveloperSettingsPresenter @Inject constructor(
|
|||
private val featureFlagService: FeatureFlagService,
|
||||
private val computeCacheSizeUseCase: ComputeCacheSizeUseCase,
|
||||
private val clearCacheUseCase: ClearCacheUseCase,
|
||||
private val rageshakePresenter: RageshakePreferencesPresenter,
|
||||
private val rageshakePresenter: Presenter<RageshakePreferencesState>,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val logoutUseCase: LogoutUseCase,
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import io.element.android.libraries.architecture.AsyncAction
|
|||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingStateNoSuccess
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.notificationsettings.NotificationSettingsService
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
|
|
@ -47,7 +47,7 @@ class NotificationSettingsPresenter @Inject constructor(
|
|||
private val matrixClient: MatrixClient,
|
||||
private val pushService: PushService,
|
||||
private val systemNotificationsEnabledProvider: SystemNotificationsEnabledProvider,
|
||||
private val fullScreenIntentPermissionsPresenter: FullScreenIntentPermissionsPresenter,
|
||||
private val fullScreenIntentPermissionsPresenter: Presenter<FullScreenIntentPermissionsState>,
|
||||
) : Presenter<NotificationSettingsState> {
|
||||
@Composable
|
||||
override fun present(): NotificationSettingsState {
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
|||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toImmutableList
|
||||
|
|
@ -87,15 +88,3 @@ fun aInvalidNotificationSettingsState(
|
|||
fullScreenIntentPermissionsState = aFullScreenIntentPermissionsState(),
|
||||
eventSink = eventSink,
|
||||
)
|
||||
|
||||
internal fun aFullScreenIntentPermissionsState(
|
||||
permissionGranted: Boolean = true,
|
||||
shouldDisplay: Boolean = false,
|
||||
openFullScreenIntentSettings: () -> Unit = {},
|
||||
dismissFullScreenIntentBanner: () -> Unit = {},
|
||||
) = FullScreenIntentPermissionsState(
|
||||
permissionGranted = permissionGranted,
|
||||
shouldDisplayBanner = shouldDisplay,
|
||||
openFullScreenIntentSettings = openFullScreenIntentSettings,
|
||||
dismissFullScreenIntentBanner = dismissFullScreenIntentBanner,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -16,7 +16,7 @@ import androidx.compose.runtime.mutableStateOf
|
|||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
|
|
@ -42,7 +42,7 @@ class PreferencesRootPresenter @Inject constructor(
|
|||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val indicatorService: IndicatorService,
|
||||
private val directLogoutPresenter: DirectLogoutPresenter,
|
||||
private val directLogoutPresenter: Presenter<DirectLogoutState>,
|
||||
private val showDeveloperSettingsProvider: ShowDeveloperSettingsProvider,
|
||||
) : Presenter<PreferencesRootState> {
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -11,9 +11,7 @@ import app.cash.molecule.RecompositionMode
|
|||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.analytics.impl.preferences.DefaultAnalyticsPreferencesPresenter
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.features.analytics.api.preferences.aAnalyticsPreferencesState
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Rule
|
||||
|
|
@ -25,15 +23,14 @@ class AnalyticsSettingsPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val analyticsPresenter = DefaultAnalyticsPreferencesPresenter(FakeAnalyticsService(), aBuildMeta())
|
||||
val presenter = AnalyticsSettingsPresenter(
|
||||
analyticsPresenter,
|
||||
analyticsPreferencesPresenter = { aAnalyticsPreferencesState() },
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
assertThat(initialState.analyticsState.isEnabled).isFalse()
|
||||
assertThat(initialState.analyticsPreferencesState.isEnabled).isFalse()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,9 +15,7 @@ import io.element.android.appconfig.ElementCallConfig
|
|||
import io.element.android.features.logout.test.FakeLogoutUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeClearCacheUseCase
|
||||
import io.element.android.features.preferences.impl.tasks.FakeComputeCacheSizeUseCase
|
||||
import io.element.android.features.rageshake.impl.preferences.DefaultRageshakePreferencesPresenter
|
||||
import io.element.android.features.rageshake.test.rageshake.FakeRageShake
|
||||
import io.element.android.features.rageshake.test.rageshake.FakeRageshakeDataStore
|
||||
import io.element.android.features.rageshake.api.preferences.aRageshakePreferencesState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
|
|
@ -54,7 +52,7 @@ class DeveloperSettingsPresenterTest {
|
|||
val loadedState = awaitItem()
|
||||
assertThat(loadedState.rageshakeState.isEnabled).isFalse()
|
||||
assertThat(loadedState.rageshakeState.isSupported).isTrue()
|
||||
assertThat(loadedState.rageshakeState.sensitivity).isEqualTo(1.0f)
|
||||
assertThat(loadedState.rageshakeState.sensitivity).isEqualTo(0.3f)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
|
@ -105,9 +103,8 @@ class DeveloperSettingsPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - clear cache`() = runTest {
|
||||
val rageshakePresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore())
|
||||
val clearCacheUseCase = FakeClearCacheUseCase()
|
||||
val presenter = createDeveloperSettingsPresenter(clearCacheUseCase = clearCacheUseCase, rageshakePresenter = rageshakePresenter)
|
||||
val presenter = createDeveloperSettingsPresenter(clearCacheUseCase = clearCacheUseCase)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -202,7 +199,6 @@ class DeveloperSettingsPresenterTest {
|
|||
featureFlagService: FakeFeatureFlagService = FakeFeatureFlagService(),
|
||||
cacheSizeUseCase: FakeComputeCacheSizeUseCase = FakeComputeCacheSizeUseCase(),
|
||||
clearCacheUseCase: FakeClearCacheUseCase = FakeClearCacheUseCase(),
|
||||
rageshakePresenter: DefaultRageshakePreferencesPresenter = DefaultRageshakePreferencesPresenter(FakeRageShake(), FakeRageshakeDataStore()),
|
||||
preferencesStore: InMemoryAppPreferencesStore = InMemoryAppPreferencesStore(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
logoutUseCase: FakeLogoutUseCase = FakeLogoutUseCase(logoutLambda = { "" })
|
||||
|
|
@ -211,7 +207,7 @@ class DeveloperSettingsPresenterTest {
|
|||
featureFlagService = featureFlagService,
|
||||
computeCacheSizeUseCase = cacheSizeUseCase,
|
||||
clearCacheUseCase = clearCacheUseCase,
|
||||
rageshakePresenter = rageshakePresenter,
|
||||
rageshakePresenter = { aRageshakePreferencesState() },
|
||||
appPreferencesStore = preferencesStore,
|
||||
buildMeta = buildMeta,
|
||||
logoutUseCase = logoutUseCase,
|
||||
|
|
|
|||
|
|
@ -12,7 +12,8 @@ import app.cash.molecule.moleculeFlow
|
|||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.fullscreenintent.test.FakeFullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.room.RoomNotificationMode
|
||||
import io.element.android.libraries.matrix.test.A_THROWABLE
|
||||
|
|
@ -263,12 +264,11 @@ class NotificationSettingsPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - RefreshSystemNotificationsEnabled also refreshes fullScreenIntentState`() = runTest {
|
||||
val fullScreenIntentPermissionsPresenter = FakeFullScreenIntentPermissionsPresenter().apply {
|
||||
state = state.copy(permissionGranted = false)
|
||||
}
|
||||
var lambdaResult = aFullScreenIntentPermissionsState(permissionGranted = false)
|
||||
val fullScreenIntentPermissionsStateLambda = { lambdaResult }
|
||||
val presenter = createNotificationSettingsPresenter(
|
||||
pushService = createFakePushService(),
|
||||
fullScreenIntentPermissionsPresenter = fullScreenIntentPermissionsPresenter,
|
||||
fullScreenIntentPermissionsStateLambda = fullScreenIntentPermissionsStateLambda,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -277,7 +277,7 @@ class NotificationSettingsPresenterTest {
|
|||
assertThat(initialState.fullScreenIntentPermissionsState.permissionGranted).isFalse()
|
||||
|
||||
// Change the notification settings
|
||||
fullScreenIntentPermissionsPresenter.state = fullScreenIntentPermissionsPresenter.state.copy(permissionGranted = true)
|
||||
lambdaResult = lambdaResult.copy(permissionGranted = true)
|
||||
// Check it's not changed unless we refresh
|
||||
expectNoEvents()
|
||||
|
||||
|
|
@ -336,7 +336,7 @@ class NotificationSettingsPresenterTest {
|
|||
private fun createNotificationSettingsPresenter(
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
pushService: PushService = FakePushService(),
|
||||
fullScreenIntentPermissionsPresenter: FakeFullScreenIntentPermissionsPresenter = FakeFullScreenIntentPermissionsPresenter()
|
||||
fullScreenIntentPermissionsStateLambda: () -> FullScreenIntentPermissionsState = { aFullScreenIntentPermissionsState() },
|
||||
): NotificationSettingsPresenter {
|
||||
val matrixClient = FakeMatrixClient(notificationSettingsService = notificationSettingsService)
|
||||
return NotificationSettingsPresenter(
|
||||
|
|
@ -345,7 +345,7 @@ class NotificationSettingsPresenterTest {
|
|||
matrixClient = matrixClient,
|
||||
pushService = pushService,
|
||||
systemNotificationsEnabledProvider = FakeSystemNotificationsEnabledProvider(),
|
||||
fullScreenIntentPermissionsPresenter = fullScreenIntentPermissionsPresenter,
|
||||
fullScreenIntentPermissionsPresenter = { fullScreenIntentPermissionsStateLambda() },
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,16 +7,13 @@
|
|||
|
||||
package io.element.android.features.preferences.impl.root
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.ReceiveTurbine
|
||||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.features.preferences.impl.utils.ShowDeveloperSettingsProvider
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.core.meta.BuildType
|
||||
import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatcher
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
|
|
@ -38,12 +35,6 @@ class PreferencesRootPresenterTest {
|
|||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
private val aDirectLogoutState = DirectLogoutState(
|
||||
canDoDirectSignOut = true,
|
||||
logoutAction = AsyncAction.Uninitialized,
|
||||
eventSink = {},
|
||||
)
|
||||
|
||||
@Test
|
||||
fun `present - initial state`() = runTest {
|
||||
val matrixClient = FakeMatrixClient(canDeactivateAccountResult = { true })
|
||||
|
|
@ -78,7 +69,7 @@ class PreferencesRootPresenterTest {
|
|||
assertThat(loadedState.showLockScreenSettings).isTrue()
|
||||
assertThat(loadedState.showNotificationSettings).isTrue()
|
||||
assertThat(loadedState.canDeactivateAccount).isTrue()
|
||||
assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState)
|
||||
assertThat(loadedState.directLogoutState).isEqualTo(aDirectLogoutState())
|
||||
assertThat(loadedState.snackbarMessage).isNull()
|
||||
}
|
||||
}
|
||||
|
|
@ -148,10 +139,7 @@ class PreferencesRootPresenterTest {
|
|||
sessionVerificationService = sessionVerificationService,
|
||||
encryptionService = FakeEncryptionService(),
|
||||
),
|
||||
directLogoutPresenter = object : DirectLogoutPresenter {
|
||||
@Composable
|
||||
override fun present() = aDirectLogoutState
|
||||
},
|
||||
directLogoutPresenter = { aDirectLogoutState() },
|
||||
showDeveloperSettingsProvider = showDeveloperSettingsProvider,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,33 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.rageshake.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.rageshake.api.crash.CrashDetectionPresenter
|
||||
import io.element.android.features.rageshake.api.crash.CrashDetectionState
|
||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionPresenter
|
||||
import io.element.android.features.rageshake.api.detection.RageshakeDetectionState
|
||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesPresenter
|
||||
import io.element.android.features.rageshake.api.preferences.RageshakePreferencesState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.AppScope
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
@Module
|
||||
interface RageshakeModule {
|
||||
@Binds
|
||||
fun bindRageshakePreferencesPresenter(presenter: RageshakePreferencesPresenter): Presenter<RageshakePreferencesState>
|
||||
|
||||
@Binds
|
||||
fun bindRageshakeDetectionPresenter(presenter: RageshakeDetectionPresenter): Presenter<RageshakeDetectionState>
|
||||
|
||||
@Binds
|
||||
fun bindCrashDetectionPresenter(presenter: CrashDetectionPresenter): Presenter<CrashDetectionState>
|
||||
}
|
||||
|
|
@ -64,7 +64,6 @@ dependencies {
|
|||
testImplementation(projects.libraries.usersearch.test)
|
||||
testImplementation(projects.libraries.featureflag.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.features.leaveroom.test)
|
||||
testImplementation(projects.features.createroom.test)
|
||||
testImplementation(projects.services.analytics.test)
|
||||
testImplementation(libs.androidx.compose.ui.test.junit)
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import androidx.compose.runtime.remember
|
|||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.messages.api.pinned.IsPinnedMessagesFeatureEnabled
|
||||
import io.element.android.features.roomdetails.impl.members.details.RoomMemberDetailsPresenter
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
|
@ -56,7 +56,7 @@ class RoomDetailsPresenter @Inject constructor(
|
|||
private val featureFlagService: FeatureFlagService,
|
||||
private val notificationSettingsService: NotificationSettingsService,
|
||||
private val roomMembersDetailsPresenterFactory: RoomMemberDetailsPresenter.Factory,
|
||||
private val leaveRoomPresenter: LeaveRoomPresenter,
|
||||
private val leaveRoomPresenter: Presenter<LeaveRoomState>,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val isPinnedMessagesFeatureEnabled: IsPinnedMessagesFeatureEnabled,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
|
||||
@Module
|
||||
@ContributesTo(RoomScope::class)
|
||||
interface RoomDetailsModule {
|
||||
@Binds
|
||||
fun bindRoomMembersModerationPresenter(presenter: RoomMembersModerationPresenter): Presenter<RoomMembersModerationState>
|
||||
}
|
||||
|
|
@ -21,7 +21,7 @@ import dagger.assisted.Assisted
|
|||
import dagger.assisted.AssistedFactory
|
||||
import dagger.assisted.AssistedInject
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
import io.element.android.libraries.architecture.AsyncData
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
|
|
@ -40,7 +40,7 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
|||
private val room: MatrixRoom,
|
||||
private val roomMemberListDataSource: RoomMemberListDataSource,
|
||||
private val coroutineDispatchers: CoroutineDispatchers,
|
||||
private val roomMembersModerationPresenter: RoomMembersModerationPresenter,
|
||||
private val roomMembersModerationPresenter: Presenter<RoomMembersModerationState>,
|
||||
@Assisted private val navigator: RoomMemberListNavigator,
|
||||
) : Presenter<RoomMemberListState> {
|
||||
@AssistedFactory
|
||||
|
|
@ -136,7 +136,7 @@ class RoomMemberListPresenter @AssistedInject constructor(
|
|||
is RoomMemberListEvents.OnSearchActiveChanged -> isSearchActive = event.active
|
||||
is RoomMemberListEvents.UpdateSearchQuery -> searchQuery = event.query
|
||||
is RoomMemberListEvents.RoomMemberSelected -> coroutineScope.launch {
|
||||
if (roomMembersModerationPresenter.canDisplayModerationActions()) {
|
||||
if (roomModerationState.canDisplayModerationActions) {
|
||||
roomModerationState.eventSink(RoomMembersModerationEvents.SelectRoomMember(event.roomMember))
|
||||
} else {
|
||||
navigator.openRoomMemberDetails(event.roomMember.userId)
|
||||
|
|
|
|||
|
|
@ -1,174 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.impl.members.moderation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.finally
|
||||
import io.element.android.libraries.di.RoomScope
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ContributesBinding(RoomScope::class)
|
||||
class DefaultRoomMembersModerationPresenter @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : RoomMembersModerationPresenter {
|
||||
private var selectedMember by mutableStateOf<RoomMember?>(null)
|
||||
|
||||
private suspend fun canBan() = room.canBan().getOrDefault(false)
|
||||
private suspend fun canKick() = room.canKick().getOrDefault(false)
|
||||
|
||||
override suspend fun canDisplayModerationActions(): Boolean {
|
||||
return !room.isDm && (canBan() || canKick())
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomMembersModerationState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var moderationActions by remember { mutableStateOf(persistentListOf<ModerationAction>()) }
|
||||
|
||||
val kickUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
val banUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
val unbanUserAsyncAction = remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
|
||||
val canDisplayBannedUsers by produceState(initialValue = false) {
|
||||
value = !room.isDm && canBan()
|
||||
}
|
||||
|
||||
fun handleEvent(event: RoomMembersModerationEvents) {
|
||||
when (event) {
|
||||
is RoomMembersModerationEvents.SelectRoomMember -> {
|
||||
coroutineScope.launch {
|
||||
selectedMember = event.roomMember
|
||||
if (event.roomMember.membership == RoomMembershipState.BAN && canBan()) {
|
||||
unbanUserAsyncAction.value = AsyncAction.Confirming
|
||||
} else {
|
||||
moderationActions = buildList {
|
||||
add(ModerationAction.DisplayProfile(event.roomMember.userId))
|
||||
val currentUserMemberPowerLevel = room.userRole(room.sessionId).getOrDefault(RoomMember.Role.USER).powerLevel
|
||||
if (currentUserMemberPowerLevel > event.roomMember.powerLevel) {
|
||||
if (canKick()) {
|
||||
add(ModerationAction.KickUser(event.roomMember.userId))
|
||||
}
|
||||
if (canBan()) {
|
||||
add(ModerationAction.BanUser(event.roomMember.userId))
|
||||
}
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.KickUser -> {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.kickUser(it.userId, kickUserAsyncAction)
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.BanUser -> {
|
||||
if (banUserAsyncAction.value.isConfirming()) {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.banUser(it.userId, banUserAsyncAction)
|
||||
}
|
||||
} else {
|
||||
banUserAsyncAction.value = AsyncAction.Confirming
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.UnbanUser -> {
|
||||
if (unbanUserAsyncAction.value.isConfirming()) {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction)
|
||||
}
|
||||
} else {
|
||||
unbanUserAsyncAction.value = AsyncAction.Confirming
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.Reset -> {
|
||||
selectedMember = null
|
||||
moderationActions = persistentListOf()
|
||||
kickUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
banUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
unbanUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RoomMembersModerationState(
|
||||
selectedRoomMember = selectedMember,
|
||||
actions = moderationActions,
|
||||
kickUserAsyncAction = kickUserAsyncAction.value,
|
||||
banUserAsyncAction = banUserAsyncAction.value,
|
||||
unbanUserAsyncAction = unbanUserAsyncAction.value,
|
||||
canDisplayBannedUsers = canDisplayBannedUsers,
|
||||
eventSink = { handleEvent(it) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.kickUser(
|
||||
userId: UserId,
|
||||
kickUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(kickUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember))
|
||||
room.kickUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun CoroutineScope.banUser(
|
||||
userId: UserId,
|
||||
banUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(banUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember))
|
||||
room.banUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun CoroutineScope.unbanUser(
|
||||
userId: UserId,
|
||||
unbanUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(unbanUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember))
|
||||
room.unbanUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(action: MutableState<AsyncAction<T>>, block: suspend () -> Result<T>) {
|
||||
launch(dispatchers.io) {
|
||||
action.runUpdatingState {
|
||||
val result = block()
|
||||
if (result.isSuccess) {
|
||||
room.membersStateFlow.drop(1).take(1)
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,20 +7,179 @@
|
|||
|
||||
package io.element.android.features.roomdetails.impl.members.moderation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.MutableState
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.produceState
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.architecture.runUpdatingState
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.core.extensions.finally
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoom
|
||||
import io.element.android.libraries.matrix.api.room.RoomMember
|
||||
import io.element.android.libraries.matrix.api.room.RoomMembershipState
|
||||
import io.element.android.libraries.matrix.api.room.isDm
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canBan
|
||||
import io.element.android.libraries.matrix.api.room.powerlevels.canKick
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.flow.drop
|
||||
import kotlinx.coroutines.flow.take
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
interface RoomMembersModerationPresenter : Presenter<RoomMembersModerationState> {
|
||||
suspend fun canDisplayModerationActions(): Boolean
|
||||
class RoomMembersModerationPresenter @Inject constructor(
|
||||
private val room: MatrixRoom,
|
||||
private val dispatchers: CoroutineDispatchers,
|
||||
private val analyticsService: AnalyticsService,
|
||||
) : Presenter<RoomMembersModerationState> {
|
||||
private var selectedMember by mutableStateOf<RoomMember?>(null)
|
||||
|
||||
fun dummyState() = RoomMembersModerationState(
|
||||
selectedRoomMember = null,
|
||||
actions = persistentListOf(),
|
||||
kickUserAsyncAction = AsyncAction.Uninitialized,
|
||||
banUserAsyncAction = AsyncAction.Uninitialized,
|
||||
unbanUserAsyncAction = AsyncAction.Uninitialized,
|
||||
canDisplayBannedUsers = false,
|
||||
eventSink = {}
|
||||
)
|
||||
private suspend fun canBan() = room.canBan().getOrDefault(false)
|
||||
private suspend fun canKick() = room.canKick().getOrDefault(false)
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomMembersModerationState {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var moderationActions by remember { mutableStateOf(persistentListOf<ModerationAction>()) }
|
||||
|
||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||
val canDisplayModerationActions by produceState(
|
||||
initialValue = false,
|
||||
key1 = syncUpdateFlow.value
|
||||
) {
|
||||
value = !room.isDm && (canBan() || canKick())
|
||||
}
|
||||
val kickUserAsyncAction =
|
||||
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
val banUserAsyncAction =
|
||||
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
val unbanUserAsyncAction =
|
||||
remember { mutableStateOf(AsyncAction.Uninitialized as AsyncAction<Unit>) }
|
||||
|
||||
val canDisplayBannedUsers by produceState(initialValue = false) {
|
||||
value = !room.isDm && canBan()
|
||||
}
|
||||
|
||||
fun handleEvent(event: RoomMembersModerationEvents) {
|
||||
when (event) {
|
||||
is RoomMembersModerationEvents.SelectRoomMember -> {
|
||||
coroutineScope.launch {
|
||||
selectedMember = event.roomMember
|
||||
if (event.roomMember.membership == RoomMembershipState.BAN && canBan()) {
|
||||
unbanUserAsyncAction.value = AsyncAction.Confirming
|
||||
} else {
|
||||
moderationActions = buildList {
|
||||
add(ModerationAction.DisplayProfile(event.roomMember.userId))
|
||||
val currentUserMemberPowerLevel = room.userRole(room.sessionId)
|
||||
.getOrDefault(RoomMember.Role.USER)
|
||||
.powerLevel
|
||||
if (currentUserMemberPowerLevel > event.roomMember.powerLevel) {
|
||||
if (canKick()) {
|
||||
add(ModerationAction.KickUser(event.roomMember.userId))
|
||||
}
|
||||
if (canBan()) {
|
||||
add(ModerationAction.BanUser(event.roomMember.userId))
|
||||
}
|
||||
}
|
||||
}.toPersistentList()
|
||||
}
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.KickUser -> {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.kickUser(it.userId, kickUserAsyncAction)
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.BanUser -> {
|
||||
if (banUserAsyncAction.value.isConfirming()) {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.banUser(it.userId, banUserAsyncAction)
|
||||
}
|
||||
} else {
|
||||
banUserAsyncAction.value = AsyncAction.Confirming
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.UnbanUser -> {
|
||||
if (unbanUserAsyncAction.value.isConfirming()) {
|
||||
moderationActions = persistentListOf()
|
||||
selectedMember?.let {
|
||||
coroutineScope.unbanUser(it.userId, unbanUserAsyncAction)
|
||||
}
|
||||
} else {
|
||||
unbanUserAsyncAction.value = AsyncAction.Confirming
|
||||
}
|
||||
}
|
||||
is RoomMembersModerationEvents.Reset -> {
|
||||
selectedMember = null
|
||||
moderationActions = persistentListOf()
|
||||
kickUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
banUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
unbanUserAsyncAction.value = AsyncAction.Uninitialized
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return RoomMembersModerationState(
|
||||
canDisplayModerationActions = canDisplayModerationActions,
|
||||
selectedRoomMember = selectedMember,
|
||||
actions = moderationActions,
|
||||
kickUserAsyncAction = kickUserAsyncAction.value,
|
||||
banUserAsyncAction = banUserAsyncAction.value,
|
||||
unbanUserAsyncAction = unbanUserAsyncAction.value,
|
||||
canDisplayBannedUsers = canDisplayBannedUsers,
|
||||
eventSink = { handleEvent(it) },
|
||||
)
|
||||
}
|
||||
|
||||
private fun CoroutineScope.kickUser(
|
||||
userId: UserId,
|
||||
kickUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(kickUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.KickMember))
|
||||
room.kickUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun CoroutineScope.banUser(
|
||||
userId: UserId,
|
||||
banUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(banUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.BanMember))
|
||||
room.banUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun CoroutineScope.unbanUser(
|
||||
userId: UserId,
|
||||
unbanUserAction: MutableState<AsyncAction<Unit>>,
|
||||
) = runActionAndWaitForMembershipChange(unbanUserAction) {
|
||||
analyticsService.capture(RoomModeration(RoomModeration.Action.UnbanMember))
|
||||
room.unbanUser(userId).finally { selectedMember = null }
|
||||
}
|
||||
|
||||
private fun <T> CoroutineScope.runActionAndWaitForMembershipChange(
|
||||
action: MutableState<AsyncAction<T>>,
|
||||
block: suspend () -> Result<T>
|
||||
) {
|
||||
launch(dispatchers.io) {
|
||||
action.runUpdatingState {
|
||||
val result = block()
|
||||
if (result.isSuccess) {
|
||||
room.membersStateFlow.drop(1).take(1)
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@ import io.element.android.libraries.matrix.api.room.RoomMember
|
|||
import kotlinx.collections.immutable.ImmutableList
|
||||
|
||||
data class RoomMembersModerationState(
|
||||
val canDisplayModerationActions: Boolean,
|
||||
val selectedRoomMember: RoomMember?,
|
||||
val actions: ImmutableList<ModerationAction>,
|
||||
val kickUserAsyncAction: AsyncAction<Unit>,
|
||||
|
|
|
|||
|
|
@ -71,6 +71,7 @@ class RoomMembersModerationStatePreviewProvider : PreviewParameterProvider<RoomM
|
|||
}
|
||||
|
||||
fun aRoomMembersModerationState(
|
||||
canDisplayModerationActions: Boolean = false,
|
||||
selectedRoomMember: RoomMember? = null,
|
||||
actions: List<ModerationAction> = emptyList(),
|
||||
kickUserAsyncAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||
|
|
@ -79,6 +80,7 @@ fun aRoomMembersModerationState(
|
|||
canDisplayBannedUsers: Boolean = false,
|
||||
eventSink: (RoomMembersModerationEvents) -> Unit = {},
|
||||
) = RoomMembersModerationState(
|
||||
canDisplayModerationActions = canDisplayModerationActions,
|
||||
selectedRoomMember = selectedRoomMember,
|
||||
actions = actions.toPersistentList(),
|
||||
kickUserAsyncAction = kickUserAsyncAction,
|
||||
|
|
|
|||
|
|
@ -16,8 +16,8 @@ import com.google.common.truth.Truth.assertThat
|
|||
import im.vector.app.features.analytics.plan.Interaction
|
||||
import io.element.android.features.createroom.test.FakeStartDMAction
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsEvent
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsPresenter
|
||||
import io.element.android.features.roomdetails.impl.RoomDetailsState
|
||||
|
|
@ -45,6 +45,7 @@ import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
|||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.services.analytics.api.AnalyticsService
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.FakeLifecycleOwner
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.consumeItemsUntilPredicate
|
||||
|
|
@ -72,7 +73,7 @@ class RoomDetailsPresenterTest {
|
|||
|
||||
private fun TestScope.createRoomDetailsPresenter(
|
||||
room: MatrixRoom = aMatrixRoom(),
|
||||
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
|
||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
notificationSettingsService: FakeNotificationSettingsService = FakeNotificationSettingsService(),
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
|
|
@ -93,7 +94,7 @@ class RoomDetailsPresenterTest {
|
|||
featureFlagService = featureFlagService,
|
||||
notificationSettingsService = matrixClient.notificationSettingsService(),
|
||||
roomMembersDetailsPresenterFactory = roomMemberDetailsPresenterFactory,
|
||||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
leaveRoomPresenter = { leaveRoomState },
|
||||
dispatchers = dispatchers,
|
||||
isPinnedMessagesFeatureEnabled = { isPinnedMessagesFeatureEnabled },
|
||||
analyticsService = analyticsService,
|
||||
|
|
@ -475,7 +476,7 @@ class RoomDetailsPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - leave room event is passed on to leave room presenter`() = runTest {
|
||||
val leaveRoomPresenter = FakeLeaveRoomPresenter()
|
||||
val leaveRoomEventRecorder = EventsRecorder<LeaveRoomEvent>()
|
||||
val room = aMatrixRoom(
|
||||
canInviteResult = { Result.success(true) },
|
||||
canUserJoinCallResult = { Result.success(true) },
|
||||
|
|
@ -483,25 +484,18 @@ class RoomDetailsPresenterTest {
|
|||
)
|
||||
val presenter = createRoomDetailsPresenter(
|
||||
room = room,
|
||||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
leaveRoomState = aLeaveRoomState(eventSink = leaveRoomEventRecorder),
|
||||
dispatchers = testCoroutineDispatchers()
|
||||
)
|
||||
presenter.test {
|
||||
awaitItem().eventSink(RoomDetailsEvent.LeaveRoom)
|
||||
|
||||
assertThat(leaveRoomPresenter.events).contains(
|
||||
LeaveRoomEvent.ShowConfirmation(
|
||||
room.roomId
|
||||
)
|
||||
)
|
||||
|
||||
leaveRoomEventRecorder.assertSingle(LeaveRoomEvent.ShowConfirmation(room.roomId))
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - notification mode changes`() = runTest {
|
||||
val leaveRoomPresenter = FakeLeaveRoomPresenter()
|
||||
val notificationSettingsService = FakeNotificationSettingsService()
|
||||
val room = aMatrixRoom(
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
|
|
@ -511,7 +505,6 @@ class RoomDetailsPresenterTest {
|
|||
)
|
||||
val presenter = createRoomDetailsPresenter(
|
||||
room = room,
|
||||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
notificationSettingsService = notificationSettingsService,
|
||||
)
|
||||
presenter.test {
|
||||
|
|
|
|||
|
|
@ -19,8 +19,8 @@ import io.element.android.features.roomdetails.impl.members.aRoomMemberList
|
|||
import io.element.android.features.roomdetails.impl.members.aVictor
|
||||
import io.element.android.features.roomdetails.impl.members.aWalter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.aRoomMembersModerationState
|
||||
import io.element.android.features.roomdetails.members.moderation.FakeRoomMembersModerationPresenter
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.designsystem.theme.components.SearchBarResultState
|
||||
import io.element.android.libraries.matrix.api.core.UserId
|
||||
|
|
@ -28,6 +28,7 @@ import io.element.android.libraries.matrix.api.room.MatrixRoom
|
|||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.tests.testutils.EventsRecorder
|
||||
import io.element.android.tests.testutils.WarmUpRule
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
|
|
@ -194,9 +195,9 @@ class RoomMemberListPresenterTest {
|
|||
@Test
|
||||
fun `present - RoomMemberSelected by default opens the room member details through the navigator`() = runTest {
|
||||
val navigator = FakeRoomMemberListNavigator()
|
||||
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = false)
|
||||
val roomMembersModerationStateLambda = { aRoomMembersModerationState(canDisplayModerationActions = false) }
|
||||
val presenter = createPresenter(
|
||||
moderationPresenter = moderationPresenter,
|
||||
roomMembersModerationStateLambda = roomMembersModerationStateLambda,
|
||||
navigator = navigator,
|
||||
matrixRoom = FakeMatrixRoom(
|
||||
updateMembersResult = { Result.success(Unit) },
|
||||
|
|
@ -215,17 +216,15 @@ class RoomMemberListPresenterTest {
|
|||
@Test
|
||||
fun `present - RoomMemberSelected will open the moderation options if the current user can use them`() = runTest {
|
||||
val navigator = FakeRoomMemberListNavigator()
|
||||
var selectRoomMemberCallCounts = 0
|
||||
val capturingState = aRoomMembersModerationState(eventSink = { event ->
|
||||
if (event is RoomMembersModerationEvents.SelectRoomMember) {
|
||||
selectRoomMemberCallCounts++
|
||||
}
|
||||
})
|
||||
val moderationPresenter = FakeRoomMembersModerationPresenter(canDisplayModerationActions = true).apply {
|
||||
givenState(capturingState)
|
||||
val eventsRecorder = EventsRecorder<RoomMembersModerationEvents>()
|
||||
val roomMembersModerationStateLambda = {
|
||||
aRoomMembersModerationState(
|
||||
canDisplayModerationActions = true,
|
||||
eventSink = eventsRecorder,
|
||||
)
|
||||
}
|
||||
val presenter = createPresenter(
|
||||
moderationPresenter = moderationPresenter,
|
||||
roomMembersModerationStateLambda = roomMembersModerationStateLambda,
|
||||
navigator = navigator,
|
||||
matrixRoom = FakeMatrixRoom(
|
||||
updateMembersResult = { Result.success(Unit) },
|
||||
|
|
@ -237,7 +236,7 @@ class RoomMemberListPresenterTest {
|
|||
}.test {
|
||||
skipItems(1)
|
||||
awaitItem().eventSink(RoomMemberListEvents.RoomMemberSelected(aVictor()))
|
||||
assertThat(selectRoomMemberCallCounts).isEqualTo(1)
|
||||
eventsRecorder.assertSingle(RoomMembersModerationEvents.SelectRoomMember(aVictor()))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -266,12 +265,12 @@ private fun TestScope.createPresenter(
|
|||
updateMembersResult = { Result.success(Unit) }
|
||||
),
|
||||
roomMemberListDataSource: RoomMemberListDataSource = createDataSource(coroutineDispatchers = coroutineDispatchers),
|
||||
moderationPresenter: FakeRoomMembersModerationPresenter = FakeRoomMembersModerationPresenter(),
|
||||
roomMembersModerationStateLambda: () -> RoomMembersModerationState = { aRoomMembersModerationState() },
|
||||
navigator: RoomMemberListNavigator = object : RoomMemberListNavigator {}
|
||||
) = RoomMemberListPresenter(
|
||||
room = matrixRoom,
|
||||
roomMemberListDataSource = roomMemberListDataSource,
|
||||
coroutineDispatchers = coroutineDispatchers,
|
||||
roomMembersModerationPresenter = moderationPresenter,
|
||||
roomMembersModerationPresenter = { roomMembersModerationStateLambda() },
|
||||
navigator = navigator
|
||||
)
|
||||
|
|
|
|||
|
|
@ -1,34 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.features.roomdetails.members.moderation
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.setValue
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationState
|
||||
|
||||
class FakeRoomMembersModerationPresenter(
|
||||
private val canDisplayModerationActions: Boolean = true,
|
||||
) : RoomMembersModerationPresenter {
|
||||
private var state by mutableStateOf(dummyState())
|
||||
|
||||
override suspend fun canDisplayModerationActions(): Boolean {
|
||||
return canDisplayModerationActions
|
||||
}
|
||||
|
||||
@Composable
|
||||
override fun present(): RoomMembersModerationState {
|
||||
return state
|
||||
}
|
||||
|
||||
fun givenState(state: RoomMembersModerationState) {
|
||||
this.state = state
|
||||
}
|
||||
}
|
||||
|
|
@ -14,9 +14,9 @@ import com.google.common.truth.Truth.assertThat
|
|||
import im.vector.app.features.analytics.plan.RoomModeration
|
||||
import io.element.android.features.roomdetails.impl.members.aRoomMember
|
||||
import io.element.android.features.roomdetails.impl.members.aVictor
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.DefaultRoomMembersModerationPresenter
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.ModerationAction
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationEvents
|
||||
import io.element.android.features.roomdetails.impl.members.moderation.RoomMembersModerationPresenter
|
||||
import io.element.android.libraries.architecture.AsyncAction
|
||||
import io.element.android.libraries.core.coroutine.CoroutineDispatchers
|
||||
import io.element.android.libraries.matrix.api.room.MatrixRoomMembersState
|
||||
|
|
@ -27,20 +27,23 @@ import io.element.android.libraries.matrix.test.A_USER_ID_2
|
|||
import io.element.android.libraries.matrix.test.room.FakeMatrixRoom
|
||||
import io.element.android.libraries.matrix.test.room.aRoomInfo
|
||||
import io.element.android.services.analytics.test.FakeAnalyticsService
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
||||
class DefaultRoomMembersModerationPresenterTest {
|
||||
class RoomMembersModerationPresenterTest {
|
||||
@Test
|
||||
fun `canDisplayModerationActions - when room is DM is false`() = runTest {
|
||||
val room = FakeMatrixRoom(isDirect = true, isPublic = true, activeMemberCount = 2).apply {
|
||||
givenRoomInfo(aRoomInfo(isDirect = true, isPublic = false, activeMembersCount = 2))
|
||||
}
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
assertThat(presenter.canDisplayModerationActions()).isFalse()
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
presenter.test {
|
||||
assertThat(awaitItem().canDisplayModerationActions).isFalse()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -51,8 +54,11 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
canKickResult = { Result.success(true) },
|
||||
canBanResult = { Result.success(true) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
assertThat(presenter.canDisplayModerationActions()).isTrue()
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().canDisplayModerationActions).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -62,8 +68,11 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
activeMemberCount = 10,
|
||||
canBanResult = { Result.success(true) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
assertThat(presenter.canDisplayModerationActions()).isTrue()
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
presenter.test {
|
||||
skipItems(1)
|
||||
assertThat(awaitItem().canDisplayModerationActions).isTrue()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
@ -74,7 +83,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
|
||||
)
|
||||
val selectedMember = aVictor()
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -101,7 +110,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
|
||||
)
|
||||
val selectedMember = aRoomMember(A_USER_ID_2, powerLevel = 100L)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -125,7 +134,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
canBanResult = { Result.success(true) },
|
||||
userRoleResult = { Result.success(RoomMember.Role.ADMIN) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -148,7 +157,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
kickUserResult = { _, _ -> Result.success(Unit) },
|
||||
)
|
||||
val selectedMember = aVictor()
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -176,7 +185,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
banUserResult = { _, _ -> Result.success(Unit) },
|
||||
)
|
||||
val selectedMember = aVictor()
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -211,7 +220,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
).apply {
|
||||
givenRoomMembersState(MatrixRoomMembersState.Ready(persistentListOf(selectedMember)))
|
||||
}
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room, analyticsService = analyticsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -237,7 +246,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
canBanResult = { Result.success(true) },
|
||||
userRoleResult = { Result.success(RoomMember.Role.USER) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -261,7 +270,7 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
unBanUserResult = { _, _ -> Result.failure(Throwable("Eek")) },
|
||||
userRoleResult = { Result.success(RoomMember.Role.USER) },
|
||||
)
|
||||
val presenter = createDefaultRoomMembersModerationPresenter(matrixRoom = room)
|
||||
val presenter = createRoomMembersModerationPresenter(matrixRoom = room)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -301,12 +310,12 @@ class DefaultRoomMembersModerationPresenterTest {
|
|||
}
|
||||
}
|
||||
|
||||
private fun TestScope.createDefaultRoomMembersModerationPresenter(
|
||||
private fun TestScope.createRoomMembersModerationPresenter(
|
||||
matrixRoom: FakeMatrixRoom = FakeMatrixRoom(),
|
||||
dispatchers: CoroutineDispatchers = testCoroutineDispatchers(),
|
||||
analyticsService: FakeAnalyticsService = FakeAnalyticsService(),
|
||||
): DefaultRoomMembersModerationPresenter {
|
||||
return DefaultRoomMembersModerationPresenter(
|
||||
): RoomMembersModerationPresenter {
|
||||
return RoomMembersModerationPresenter(
|
||||
room = matrixRoom,
|
||||
dispatchers = dispatchers,
|
||||
analyticsService = analyticsService,
|
||||
|
|
@ -65,7 +65,6 @@ dependencies {
|
|||
testImplementation(projects.libraries.dateformatter.test)
|
||||
testImplementation(projects.libraries.eventformatter.test)
|
||||
testImplementation(projects.libraries.indicator.impl)
|
||||
testImplementation(projects.libraries.fullscreenintent.test)
|
||||
testImplementation(projects.libraries.permissions.noop)
|
||||
testImplementation(projects.libraries.permissions.test)
|
||||
testImplementation(projects.libraries.preferences.test)
|
||||
|
|
@ -75,5 +74,4 @@ dependencies {
|
|||
testImplementation(projects.features.networkmonitor.test)
|
||||
testImplementation(projects.features.logout.test)
|
||||
testImplementation(projects.tests.testutils)
|
||||
testImplementation(projects.features.leaveroom.test)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ package io.element.android.features.roomlist.impl
|
|||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import io.element.android.features.roomlist.impl.model.RoomListRoomSummary
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
|
||||
|
|
@ -37,15 +38,3 @@ internal fun aRoomsContentState(
|
|||
internal fun aSkeletonContentState() = RoomListContentState.Skeleton(16)
|
||||
|
||||
internal fun anEmptyContentState() = RoomListContentState.Empty
|
||||
|
||||
internal fun aFullScreenIntentPermissionsState(
|
||||
permissionGranted: Boolean = true,
|
||||
shouldDisplay: Boolean = false,
|
||||
openFullScreenIntentSettings: () -> Unit = {},
|
||||
dismissFullScreenIntentBanner: () -> Unit = {},
|
||||
) = FullScreenIntentPermissionsState(
|
||||
permissionGranted = permissionGranted,
|
||||
shouldDisplayBanner = shouldDisplay,
|
||||
openFullScreenIntentSettings = openFullScreenIntentSettings,
|
||||
dismissFullScreenIntentBanner = dismissFullScreenIntentBanner,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -28,8 +28,8 @@ import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
|||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.InviteData
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutState
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.api.NetworkStatus
|
||||
import io.element.android.features.roomlist.impl.datasource.RoomListDataSource
|
||||
|
|
@ -44,7 +44,7 @@ import io.element.android.libraries.designsystem.utils.snackbar.SnackbarDispatch
|
|||
import io.element.android.libraries.designsystem.utils.snackbar.collectSnackbarMessageAsState
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlags
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.indicator.api.IndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -80,7 +80,7 @@ class RoomListPresenter @Inject constructor(
|
|||
private val client: MatrixClient,
|
||||
private val networkMonitor: NetworkMonitor,
|
||||
private val snackbarDispatcher: SnackbarDispatcher,
|
||||
private val leaveRoomPresenter: LeaveRoomPresenter,
|
||||
private val leaveRoomPresenter: Presenter<LeaveRoomState>,
|
||||
private val roomListDataSource: RoomListDataSource,
|
||||
private val featureFlagService: FeatureFlagService,
|
||||
private val indicatorService: IndicatorService,
|
||||
|
|
@ -89,9 +89,9 @@ class RoomListPresenter @Inject constructor(
|
|||
private val sessionPreferencesStore: SessionPreferencesStore,
|
||||
private val analyticsService: AnalyticsService,
|
||||
private val acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState>,
|
||||
private val fullScreenIntentPermissionsPresenter: FullScreenIntentPermissionsPresenter,
|
||||
private val fullScreenIntentPermissionsPresenter: Presenter<FullScreenIntentPermissionsState>,
|
||||
private val notificationCleaner: NotificationCleaner,
|
||||
private val logoutPresenter: DirectLogoutPresenter,
|
||||
private val logoutPresenter: Presenter<DirectLogoutState>,
|
||||
) : Presenter<RoomListState> {
|
||||
private val encryptionService: EncryptionService = client.encryptionService()
|
||||
private val syncService: SyncService = client.syncService()
|
||||
|
|
|
|||
|
|
@ -10,11 +10,11 @@ package io.element.android.features.roomlist.impl.components
|
|||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import io.element.android.features.roomlist.impl.R
|
||||
import io.element.android.features.roomlist.impl.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.designsystem.atomic.molecules.DialogLikeBannerMolecule
|
||||
import io.element.android.libraries.designsystem.preview.ElementPreview
|
||||
import io.element.android.libraries.designsystem.preview.PreviewsDayNight
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
|
||||
@Composable
|
||||
fun FullScreenIntentPermissionBanner(state: FullScreenIntentPermissionsState) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
|
||||
package io.element.android.features.roomlist.impl
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import app.cash.molecule.RecompositionMode
|
||||
import app.cash.molecule.moleculeFlow
|
||||
import app.cash.turbine.test
|
||||
|
|
@ -17,9 +16,8 @@ import io.element.android.features.invite.api.response.AcceptDeclineInviteEvents
|
|||
import io.element.android.features.invite.api.response.AcceptDeclineInviteState
|
||||
import io.element.android.features.invite.api.response.anAcceptDeclineInviteState
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomEvent
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomPresenter
|
||||
import io.element.android.features.leaveroom.fake.FakeLeaveRoomPresenter
|
||||
import io.element.android.features.logout.api.direct.DirectLogoutPresenter
|
||||
import io.element.android.features.leaveroom.api.LeaveRoomState
|
||||
import io.element.android.features.leaveroom.api.aLeaveRoomState
|
||||
import io.element.android.features.logout.api.direct.aDirectLogoutState
|
||||
import io.element.android.features.networkmonitor.api.NetworkMonitor
|
||||
import io.element.android.features.networkmonitor.test.FakeNetworkMonitor
|
||||
|
|
@ -40,7 +38,7 @@ import io.element.android.libraries.eventformatter.api.RoomLastMessageFormatter
|
|||
import io.element.android.libraries.eventformatter.test.FakeRoomLastMessageFormatter
|
||||
import io.element.android.libraries.featureflag.api.FeatureFlagService
|
||||
import io.element.android.libraries.featureflag.test.FakeFeatureFlagService
|
||||
import io.element.android.libraries.fullscreenintent.test.FakeFullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -84,10 +82,7 @@ import io.element.android.tests.testutils.lambda.lambdaRecorder
|
|||
import io.element.android.tests.testutils.lambda.value
|
||||
import io.element.android.tests.testutils.test
|
||||
import io.element.android.tests.testutils.testCoroutineDispatchers
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.test.TestScope
|
||||
import kotlinx.coroutines.test.advanceTimeBy
|
||||
|
|
@ -102,7 +97,6 @@ class RoomListPresenterTest {
|
|||
|
||||
@Test
|
||||
fun `present - should start with no user and then load user with success`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val matrixClient = FakeMatrixClient(
|
||||
userDisplayName = null,
|
||||
userAvatarUrl = null,
|
||||
|
|
@ -110,7 +104,6 @@ class RoomListPresenterTest {
|
|||
matrixClient.givenGetProfileResult(matrixClient.sessionId, Result.success(MatrixUser(matrixClient.sessionId, A_USER_NAME, AN_AVATAR_URL)))
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -122,13 +115,11 @@ class RoomListPresenterTest {
|
|||
assertThat(withUserState.matrixUser.displayName).isEqualTo(A_USER_NAME)
|
||||
assertThat(withUserState.matrixUser.avatarUrl).isEqualTo(AN_AVATAR_URL)
|
||||
assertThat(withUserState.showAvatarIndicator).isTrue()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show avatar indicator`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val encryptionService = FakeEncryptionService()
|
||||
val sessionVerificationService = FakeSessionVerificationService()
|
||||
val matrixClient = FakeMatrixClient(
|
||||
|
|
@ -137,7 +128,6 @@ class RoomListPresenterTest {
|
|||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -148,7 +138,6 @@ class RoomListPresenterTest {
|
|||
encryptionService.emitBackupState(BackupState.ENABLED)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.showAvatarIndicator).isFalse()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -159,8 +148,7 @@ class RoomListPresenterTest {
|
|||
userAvatarUrl = null,
|
||||
)
|
||||
matrixClient.givenGetProfileResult(matrixClient.sessionId, Result.failure(AN_EXCEPTION))
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope)
|
||||
val presenter = createRoomListPresenter(client = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -176,8 +164,7 @@ class RoomListPresenterTest {
|
|||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService
|
||||
)
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope)
|
||||
val presenter = createRoomListPresenter(client = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -202,13 +189,11 @@ class RoomListPresenterTest {
|
|||
)
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - handle DismissRequestVerificationPrompt`() = runTest {
|
||||
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
||||
val roomListService = FakeRoomListService().apply {
|
||||
postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
}
|
||||
|
|
@ -218,7 +203,6 @@ class RoomListPresenterTest {
|
|||
val syncService = FakeSyncService(MutableStateFlow(SyncState.Running))
|
||||
val presenter = createRoomListPresenter(
|
||||
client = FakeMatrixClient(roomListService = roomListService, encryptionService = encryptionService, syncService = syncService),
|
||||
coroutineScope = scope,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -230,7 +214,6 @@ class RoomListPresenterTest {
|
|||
assertThat(eventWithContentAsRooms.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.RecoveryKeyConfirmation)
|
||||
eventSink(RoomListEvents.DismissRequestVerificationPrompt)
|
||||
assertThat(awaitItem().contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -250,10 +233,8 @@ class RoomListPresenterTest {
|
|||
},
|
||||
syncService = FakeSyncService(MutableStateFlow(SyncState.Running)),
|
||||
)
|
||||
val scope = CoroutineScope(context = coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
|
|
@ -279,18 +260,16 @@ class RoomListPresenterTest {
|
|||
nextState.eventSink(RoomListEvents.DismissBanner)
|
||||
val finalState = awaitItem()
|
||||
assertThat(finalState.contentAsRooms().securityBannerState).isEqualTo(SecurityBannerState.None)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - show context menu`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val room = FakeMatrixRoom()
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val presenter = createRoomListPresenter(client = client, coroutineScope = scope)
|
||||
val presenter = createRoomListPresenter(client = client)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -328,18 +307,16 @@ class RoomListPresenterTest {
|
|||
)
|
||||
)
|
||||
}
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - hide context menu`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val room = FakeMatrixRoom()
|
||||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val presenter = createRoomListPresenter(client = client, coroutineScope = scope)
|
||||
val presenter = createRoomListPresenter(client = client)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -364,23 +341,22 @@ class RoomListPresenterTest {
|
|||
|
||||
val hiddenState = awaitItem()
|
||||
assertThat(hiddenState.contextMenu).isEqualTo(RoomListState.ContextMenu.Hidden)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - leave room calls into leave room presenter`() = runTest {
|
||||
val leaveRoomPresenter = FakeLeaveRoomPresenter()
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(leaveRoomPresenter = leaveRoomPresenter, coroutineScope = scope)
|
||||
val leaveRoomEventsRecorder = EventsRecorder<LeaveRoomEvent>()
|
||||
val presenter = createRoomListPresenter(
|
||||
leaveRoomState = aLeaveRoomState(eventSink = leaveRoomEventsRecorder),
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
val initialState = awaitItem()
|
||||
initialState.eventSink(RoomListEvents.LeaveRoom(A_ROOM_ID))
|
||||
assertThat(leaveRoomPresenter.events).containsExactly(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID))
|
||||
leaveRoomEventsRecorder.assertSingle(LeaveRoomEvent.ShowConfirmation(A_ROOM_ID))
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -392,9 +368,7 @@ class RoomListPresenterTest {
|
|||
eventSink = eventRecorder
|
||||
)
|
||||
}
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(
|
||||
coroutineScope = scope,
|
||||
searchPresenter = searchPresenter,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
|
|
@ -413,7 +387,6 @@ class RoomListPresenterTest {
|
|||
RoomListSearchEvents.ToggleSearchVisibility
|
||||
)
|
||||
)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -428,8 +401,7 @@ class RoomListPresenterTest {
|
|||
roomListService = roomListService,
|
||||
notificationSettingsService = notificationSettingsService
|
||||
)
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val presenter = createRoomListPresenter(client = matrixClient, coroutineScope = scope)
|
||||
val presenter = createRoomListPresenter(client = matrixClient)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -443,13 +415,11 @@ class RoomListPresenterTest {
|
|||
val room = updatedState.contentAsRooms().summaries.find { it.id == A_ROOM_ID.value }
|
||||
assertThat(room?.userDefinedNotificationMode).isEqualTo(userDefinedMode)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when set is favorite event is emitted, then the action is called`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val setIsFavoriteResult = lambdaRecorder { _: Boolean -> Result.success(Unit) }
|
||||
val room = FakeMatrixRoom(
|
||||
setIsFavoriteResult = setIsFavoriteResult
|
||||
|
|
@ -458,7 +428,7 @@ class RoomListPresenterTest {
|
|||
val client = FakeMatrixClient().apply {
|
||||
givenGetRoomResult(A_ROOM_ID, room)
|
||||
}
|
||||
val presenter = createRoomListPresenter(client = client, coroutineScope = scope, analyticsService = analyticsService)
|
||||
val presenter = createRoomListPresenter(client = client, analyticsService = analyticsService)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
|
|
@ -476,13 +446,11 @@ class RoomListPresenterTest {
|
|||
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuFavouriteToggle)
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `present - when room service returns no room, then contentState is Empty`() = runTest {
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val roomListService = FakeRoomListService()
|
||||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(0))
|
||||
val matrixClient = FakeMatrixClient(
|
||||
|
|
@ -490,13 +458,11 @@ class RoomListPresenterTest {
|
|||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
)
|
||||
moleculeFlow(RecompositionMode.Immediate) {
|
||||
presenter.present()
|
||||
}.test {
|
||||
assertThat(awaitItem().contentState).isInstanceOf(RoomListContentState.Empty::class.java)
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -513,14 +479,12 @@ class RoomListPresenterTest {
|
|||
givenGetRoomResult(A_ROOM_ID_3, room3)
|
||||
}
|
||||
val analyticsService = FakeAnalyticsService()
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val clearMessagesForRoomLambda = lambdaRecorder<SessionId, RoomId, Unit> { _, _ -> }
|
||||
val notificationCleaner = FakeNotificationCleaner(
|
||||
clearMessagesForRoomLambda = clearMessagesForRoomLambda,
|
||||
)
|
||||
val presenter = createRoomListPresenter(
|
||||
client = matrixClient,
|
||||
coroutineScope = scope,
|
||||
sessionPreferencesStore = sessionPreferencesStore,
|
||||
analyticsService = analyticsService,
|
||||
notificationCleaner = notificationCleaner,
|
||||
|
|
@ -557,7 +521,6 @@ class RoomListPresenterTest {
|
|||
Interaction(name = Interaction.Name.MobileRoomListRoomContextMenuUnreadToggle),
|
||||
)
|
||||
cancelAndIgnoreRemainingEvents()
|
||||
scope.cancel()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -568,7 +531,6 @@ class RoomListPresenterTest {
|
|||
anAcceptDeclineInviteState(eventSink = eventSinkRecorder)
|
||||
}
|
||||
val roomListService = FakeRoomListService()
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
|
|
@ -578,7 +540,6 @@ class RoomListPresenterTest {
|
|||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
val presenter = createRoomListPresenter(
|
||||
coroutineScope = scope,
|
||||
client = matrixClient,
|
||||
acceptDeclineInvitePresenter = acceptDeclinePresenter
|
||||
)
|
||||
|
|
@ -609,7 +570,6 @@ class RoomListPresenterTest {
|
|||
fun `present - UpdateVisibleRange will cancel the previous subscription if called too soon`() = runTest {
|
||||
val subscribeToVisibleRoomsLambda = lambdaRecorder { _: List<RoomId> -> }
|
||||
val roomListService = FakeRoomListService(subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda)
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
|
|
@ -619,7 +579,6 @@ class RoomListPresenterTest {
|
|||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
val presenter = createRoomListPresenter(
|
||||
coroutineScope = scope,
|
||||
client = matrixClient,
|
||||
)
|
||||
presenter.test {
|
||||
|
|
@ -640,7 +599,6 @@ class RoomListPresenterTest {
|
|||
fun `present - UpdateVisibleRange subscribes to rooms in visible range`() = runTest {
|
||||
val subscribeToVisibleRoomsLambda = lambdaRecorder { _: List<RoomId> -> }
|
||||
val roomListService = FakeRoomListService(subscribeToVisibleRoomsLambda = subscribeToVisibleRoomsLambda)
|
||||
val scope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val matrixClient = FakeMatrixClient(
|
||||
roomListService = roomListService,
|
||||
)
|
||||
|
|
@ -650,7 +608,6 @@ class RoomListPresenterTest {
|
|||
roomListService.postAllRoomsLoadingState(RoomList.LoadingState.Loaded(1))
|
||||
roomListService.postAllRooms(listOf(roomSummary))
|
||||
val presenter = createRoomListPresenter(
|
||||
coroutineScope = scope,
|
||||
client = matrixClient,
|
||||
)
|
||||
presenter.test {
|
||||
|
|
@ -673,28 +630,23 @@ class RoomListPresenterTest {
|
|||
client: MatrixClient = FakeMatrixClient(),
|
||||
networkMonitor: NetworkMonitor = FakeNetworkMonitor(),
|
||||
snackbarDispatcher: SnackbarDispatcher = SnackbarDispatcher(),
|
||||
leaveRoomPresenter: LeaveRoomPresenter = FakeLeaveRoomPresenter(),
|
||||
leaveRoomState: LeaveRoomState = aLeaveRoomState(),
|
||||
lastMessageTimestampFormatter: LastMessageTimestampFormatter = FakeLastMessageTimestampFormatter().apply {
|
||||
givenFormat(A_FORMATTED_DATE)
|
||||
},
|
||||
roomLastMessageFormatter: RoomLastMessageFormatter = FakeRoomLastMessageFormatter(),
|
||||
sessionPreferencesStore: SessionPreferencesStore = InMemorySessionPreferencesStore(),
|
||||
featureFlagService: FeatureFlagService = FakeFeatureFlagService(),
|
||||
coroutineScope: CoroutineScope,
|
||||
analyticsService: AnalyticsService = FakeAnalyticsService(),
|
||||
filtersPresenter: Presenter<RoomListFiltersState> = Presenter { aRoomListFiltersState() },
|
||||
searchPresenter: Presenter<RoomListSearchState> = Presenter { aRoomListSearchState() },
|
||||
acceptDeclineInvitePresenter: Presenter<AcceptDeclineInviteState> = Presenter { anAcceptDeclineInviteState() },
|
||||
notificationCleaner: NotificationCleaner = FakeNotificationCleaner(),
|
||||
logoutPresenter: DirectLogoutPresenter = object : DirectLogoutPresenter {
|
||||
@Composable
|
||||
override fun present() = aDirectLogoutState()
|
||||
},
|
||||
) = RoomListPresenter(
|
||||
client = client,
|
||||
networkMonitor = networkMonitor,
|
||||
snackbarDispatcher = snackbarDispatcher,
|
||||
leaveRoomPresenter = leaveRoomPresenter,
|
||||
leaveRoomPresenter = { leaveRoomState },
|
||||
roomListDataSource = RoomListDataSource(
|
||||
roomListService = client.roomListService,
|
||||
roomListRoomSummaryFactory = RoomListRoomSummaryFactory(
|
||||
|
|
@ -703,7 +655,7 @@ class RoomListPresenterTest {
|
|||
),
|
||||
coroutineDispatchers = testCoroutineDispatchers(),
|
||||
notificationSettingsService = client.notificationSettingsService(),
|
||||
appScope = coroutineScope
|
||||
appScope = backgroundScope
|
||||
),
|
||||
featureFlagService = featureFlagService,
|
||||
indicatorService = DefaultIndicatorService(
|
||||
|
|
@ -715,8 +667,8 @@ class RoomListPresenterTest {
|
|||
filtersPresenter = filtersPresenter,
|
||||
analyticsService = analyticsService,
|
||||
acceptDeclineInvitePresenter = acceptDeclineInvitePresenter,
|
||||
fullScreenIntentPermissionsPresenter = FakeFullScreenIntentPermissionsPresenter(),
|
||||
fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() },
|
||||
notificationCleaner = notificationCleaner,
|
||||
logoutPresenter = logoutPresenter,
|
||||
logoutPresenter = { aDirectLogoutState() },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,12 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.fullscreenintent.api
|
||||
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
|
||||
interface FullScreenIntentPermissionsPresenter : Presenter<FullScreenIntentPermissionsState>
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.fullscreenintent.api
|
||||
|
||||
fun aFullScreenIntentPermissionsState(
|
||||
permissionGranted: Boolean = true,
|
||||
shouldDisplay: Boolean = false,
|
||||
openFullScreenIntentSettings: () -> Unit = {},
|
||||
dismissFullScreenIntentBanner: () -> Unit = {},
|
||||
) = FullScreenIntentPermissionsState(
|
||||
permissionGranted = permissionGranted,
|
||||
shouldDisplayBanner = shouldDisplay,
|
||||
openFullScreenIntentSettings = openFullScreenIntentSettings,
|
||||
dismissFullScreenIntentBanner = dismissFullScreenIntentBanner,
|
||||
)
|
||||
|
|
@ -27,7 +27,6 @@ dependencies {
|
|||
implementation(projects.services.toolbox.api)
|
||||
implementation(libs.androidx.datastore.preferences)
|
||||
|
||||
testImplementation(projects.libraries.fullscreenintent.test)
|
||||
testImplementation(libs.test.junit)
|
||||
testImplementation(libs.coroutines.test)
|
||||
testImplementation(libs.molecule.runtime)
|
||||
|
|
|
|||
|
|
@ -19,11 +19,10 @@ import androidx.compose.runtime.rememberCoroutineScope
|
|||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.datastore.preferences.core.booleanPreferencesKey
|
||||
import androidx.datastore.preferences.core.edit
|
||||
import com.squareup.anvil.annotations.ContributesBinding
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.di.SingleIn
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.preferences.api.store.PreferenceDataStoreFactory
|
||||
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
|
||||
|
|
@ -33,14 +32,13 @@ import kotlinx.coroutines.launch
|
|||
import javax.inject.Inject
|
||||
|
||||
@SingleIn(AppScope::class)
|
||||
@ContributesBinding(AppScope::class)
|
||||
class DefaultFullScreenIntentPermissionsPresenter @Inject constructor(
|
||||
class FullScreenIntentPermissionsPresenter @Inject constructor(
|
||||
private val buildVersionSdkIntProvider: BuildVersionSdkIntProvider,
|
||||
private val externalIntentLauncher: ExternalIntentLauncher,
|
||||
private val buildMeta: BuildMeta,
|
||||
private val notificationManagerCompat: NotificationManagerCompat,
|
||||
preferencesDataStoreFactory: PreferenceDataStoreFactory,
|
||||
) : FullScreenIntentPermissionsPresenter {
|
||||
) : Presenter<FullScreenIntentPermissionsState> {
|
||||
companion object {
|
||||
private const val PREF_KEY_FULL_SCREEN_INTENT_BANNER_DISMISSED = "PREF_KEY_FULL_SCREEN_INTENT_BANNER_DISMISSED"
|
||||
}
|
||||
|
|
@ -0,0 +1,23 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.fullscreenintent.impl.di
|
||||
|
||||
import com.squareup.anvil.annotations.ContributesTo
|
||||
import dagger.Binds
|
||||
import dagger.Module
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import io.element.android.libraries.di.AppScope
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.fullscreenintent.impl.FullScreenIntentPermissionsPresenter
|
||||
|
||||
@ContributesTo(AppScope::class)
|
||||
@Module
|
||||
interface FullScreenIntentModule {
|
||||
@Binds
|
||||
fun bindFullScreenIntentPermissionsPresenter(presenter: FullScreenIntentPermissionsPresenter): Presenter<FullScreenIntentPermissionsState>
|
||||
}
|
||||
|
|
@ -15,7 +15,7 @@ import app.cash.molecule.moleculeFlow
|
|||
import app.cash.turbine.test
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import io.element.android.libraries.core.meta.BuildMeta
|
||||
import io.element.android.libraries.fullscreenintent.impl.DefaultFullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.impl.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.matrix.test.core.aBuildMeta
|
||||
import io.element.android.libraries.preferences.test.FakePreferenceDataStoreFactory
|
||||
import io.element.android.services.toolbox.api.intent.ExternalIntentLauncher
|
||||
|
|
@ -32,7 +32,7 @@ import org.junit.Rule
|
|||
import org.junit.Test
|
||||
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
class DefaultFullScreenIntentPermissionsPresenterTest {
|
||||
class FullScreenIntentPermissionsPresenterTest {
|
||||
@get:Rule
|
||||
val warmUpRule = WarmUpRule()
|
||||
|
||||
|
|
@ -129,7 +129,7 @@ class DefaultFullScreenIntentPermissionsPresenterTest {
|
|||
externalIntentLauncher: ExternalIntentLauncher = FakeExternalIntentLauncher(),
|
||||
buildMeta: BuildMeta = aBuildMeta(),
|
||||
notificationManagerCompat: NotificationManagerCompat = mockk(relaxed = true)
|
||||
) = DefaultFullScreenIntentPermissionsPresenter(
|
||||
) = FullScreenIntentPermissionsPresenter(
|
||||
buildVersionSdkIntProvider = buildVersionSdkIntProvider,
|
||||
externalIntentLauncher = externalIntentLauncher,
|
||||
buildMeta = buildMeta,
|
||||
|
|
@ -1,19 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
plugins {
|
||||
id("io.element.android-compose-library")
|
||||
}
|
||||
|
||||
android {
|
||||
namespace = "io.element.android.libraries.fullscreenintent.test"
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.libraries.fullscreenintent.api)
|
||||
implementation(projects.libraries.architecture)
|
||||
}
|
||||
|
|
@ -1,25 +0,0 @@
|
|||
/*
|
||||
* Copyright 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.libraries.fullscreenintent.test
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
|
||||
class FakeFullScreenIntentPermissionsPresenter : FullScreenIntentPermissionsPresenter {
|
||||
var state = FullScreenIntentPermissionsState(
|
||||
permissionGranted = true,
|
||||
shouldDisplayBanner = false,
|
||||
dismissFullScreenIntentBanner = {},
|
||||
openFullScreenIntentSettings = {},
|
||||
)
|
||||
@Composable
|
||||
override fun present(): FullScreenIntentPermissionsState {
|
||||
return state
|
||||
}
|
||||
}
|
||||
|
|
@ -24,10 +24,7 @@ import io.element.android.libraries.push.test.notifications.FakeImageLoaderHolde
|
|||
import io.element.android.services.appnavstate.test.FakeAppNavigationStateService
|
||||
import io.element.android.tests.testutils.lambda.lambdaRecorder
|
||||
import io.mockk.mockk
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.SupervisorJob
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.test.runCurrent
|
||||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.Test
|
||||
|
|
@ -39,7 +36,6 @@ class DefaultOnMissedCallNotificationHandlerTest {
|
|||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
@Test
|
||||
fun `addMissedCallNotification - should add missed call notification`() = runTest {
|
||||
val childScope = CoroutineScope(coroutineContext + SupervisorJob())
|
||||
val dataFactory = FakeNotificationDataFactory(
|
||||
messageEventToNotificationsResult = lambdaRecorder { _, _, _ -> emptyList() }
|
||||
)
|
||||
|
|
@ -59,7 +55,7 @@ class DefaultOnMissedCallNotificationHandlerTest {
|
|||
notificationDataFactory = dataFactory,
|
||||
),
|
||||
appNavigationStateService = FakeAppNavigationStateService(),
|
||||
coroutineScope = childScope,
|
||||
coroutineScope = backgroundScope,
|
||||
matrixClientProvider = FakeMatrixClientProvider(),
|
||||
imageLoaderHolder = FakeImageLoaderHolder(),
|
||||
activeNotificationsProvider = FakeActiveNotificationsProvider(),
|
||||
|
|
@ -76,8 +72,5 @@ class DefaultOnMissedCallNotificationHandlerTest {
|
|||
runCurrent()
|
||||
|
||||
dataFactory.messageEventToNotificationsResult.assertions().isCalledOnce()
|
||||
|
||||
// Cancel the coroutine scope so the test can finish
|
||||
childScope.cancel()
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -13,8 +13,8 @@ import androidx.compose.runtime.DisposableEffect
|
|||
import androidx.compose.ui.Modifier
|
||||
import io.element.android.features.invite.impl.response.AcceptDeclineInvitePresenter
|
||||
import io.element.android.features.invite.impl.response.AcceptDeclineInviteView
|
||||
import io.element.android.features.leaveroom.impl.DefaultLeaveRoomPresenter
|
||||
import io.element.android.features.logout.impl.direct.DefaultDirectLogoutPresenter
|
||||
import io.element.android.features.leaveroom.impl.LeaveRoomPresenter
|
||||
import io.element.android.features.logout.impl.direct.DirectLogoutPresenter
|
||||
import io.element.android.features.networkmonitor.impl.DefaultNetworkMonitor
|
||||
import io.element.android.features.roomlist.impl.RoomListPresenter
|
||||
import io.element.android.features.roomlist.impl.RoomListView
|
||||
|
|
@ -33,8 +33,7 @@ import io.element.android.libraries.eventformatter.impl.DefaultRoomLastMessageFo
|
|||
import io.element.android.libraries.eventformatter.impl.ProfileChangeContentFormatter
|
||||
import io.element.android.libraries.eventformatter.impl.RoomMembershipContentFormatter
|
||||
import io.element.android.libraries.eventformatter.impl.StateContentFormatter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsPresenter
|
||||
import io.element.android.libraries.fullscreenintent.api.FullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.fullscreenintent.api.aFullScreenIntentPermissionsState
|
||||
import io.element.android.libraries.indicator.impl.DefaultIndicatorService
|
||||
import io.element.android.libraries.matrix.api.MatrixClient
|
||||
import io.element.android.libraries.matrix.api.core.RoomId
|
||||
|
|
@ -87,7 +86,7 @@ class RoomListScreen(
|
|||
client = matrixClient,
|
||||
networkMonitor = DefaultNetworkMonitor(context, Singleton.appScope),
|
||||
snackbarDispatcher = SnackbarDispatcher(),
|
||||
leaveRoomPresenter = DefaultLeaveRoomPresenter(matrixClient, RoomMembershipObserver(), coroutineDispatchers),
|
||||
leaveRoomPresenter = LeaveRoomPresenter(matrixClient, RoomMembershipObserver(), coroutineDispatchers),
|
||||
roomListDataSource = RoomListDataSource(
|
||||
roomListService = matrixClient.roomListService,
|
||||
roomListRoomSummaryFactory = roomListRoomSummaryFactory,
|
||||
|
|
@ -123,19 +122,9 @@ class RoomListScreen(
|
|||
notificationCleaner = FakeNotificationCleaner(),
|
||||
),
|
||||
analyticsService = NoopAnalyticsService(),
|
||||
fullScreenIntentPermissionsPresenter = object : FullScreenIntentPermissionsPresenter {
|
||||
@Composable
|
||||
override fun present(): FullScreenIntentPermissionsState {
|
||||
return FullScreenIntentPermissionsState(
|
||||
permissionGranted = true,
|
||||
shouldDisplayBanner = false,
|
||||
dismissFullScreenIntentBanner = {},
|
||||
openFullScreenIntentSettings = {}
|
||||
)
|
||||
}
|
||||
},
|
||||
fullScreenIntentPermissionsPresenter = { aFullScreenIntentPermissionsState() },
|
||||
notificationCleaner = FakeNotificationCleaner(),
|
||||
logoutPresenter = DefaultDirectLogoutPresenter(matrixClient, encryptionService),
|
||||
logoutPresenter = DirectLogoutPresenter(matrixClient, encryptionService),
|
||||
)
|
||||
|
||||
@Composable
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*
|
||||
* Copyright 2023, 2024 New Vector Ltd.
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
* Please see LICENSE in the repository root for full details.
|
||||
*/
|
||||
|
||||
package io.element.android.tests.konsist
|
||||
|
||||
import com.lemonappdev.konsist.api.Konsist
|
||||
import com.lemonappdev.konsist.api.ext.list.constructors
|
||||
import com.lemonappdev.konsist.api.ext.list.withAllParentsOf
|
||||
import com.lemonappdev.konsist.api.verify.assertTrue
|
||||
import io.element.android.libraries.architecture.Presenter
|
||||
import org.junit.Test
|
||||
|
||||
class KonsistPresenterTest {
|
||||
@Test
|
||||
fun `'Presenter' should not depend on other presenters`() {
|
||||
Konsist.scopeFromProject()
|
||||
.classes()
|
||||
.withAllParentsOf(Presenter::class)
|
||||
.constructors
|
||||
.assertTrue { constructor ->
|
||||
val result = constructor.parameters.none { parameter ->
|
||||
parameter.type.name.endsWith("Presenter")
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -7,16 +7,32 @@
|
|||
|
||||
package io.element.android.tests.konsist
|
||||
|
||||
import com.google.common.truth.Truth.assertThat
|
||||
import com.lemonappdev.konsist.api.Konsist
|
||||
import com.lemonappdev.konsist.api.ext.list.modifierprovider.withoutOverrideModifier
|
||||
import com.lemonappdev.konsist.api.ext.list.withAnnotationOf
|
||||
import com.lemonappdev.konsist.api.ext.list.withFunction
|
||||
import com.lemonappdev.konsist.api.ext.list.withReturnType
|
||||
import com.lemonappdev.konsist.api.ext.list.withoutAnnotationOf
|
||||
import com.lemonappdev.konsist.api.ext.list.withoutName
|
||||
import com.lemonappdev.konsist.api.verify.assertFalse
|
||||
import com.lemonappdev.konsist.api.verify.assertTrue
|
||||
import org.junit.Ignore
|
||||
import org.junit.Test
|
||||
|
||||
class KonsistTestTest {
|
||||
@Test
|
||||
fun `Ensure that unit tests are detected`() {
|
||||
val numberOfTests = Konsist
|
||||
.scopeFromTest()
|
||||
.functions()
|
||||
.withAnnotationOf(Test::class)
|
||||
.withoutAnnotationOf(Ignore::class)
|
||||
.size
|
||||
println("Number of unit tests: $numberOfTests")
|
||||
assertThat(numberOfTests).isGreaterThan(2000)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `Classes name containing @Test must end with 'Test'`() {
|
||||
Konsist
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue