Move the "Blocked users" item to the Moderation and safety screen.

The item is removed from the account section of the root settings screen and
added at the bottom of the Moderation and safety screen. As before, it is only
shown when at least one user is blocked, with the number of blocked users.

ModerationAndSafetyPresenter now computes the number of blocked users, and
ModerationAndSafetyNode exposes a Callback to navigate to the blocked users
screen. Tests are moved accordingly.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
Benoit Marty
2026-09-23 16:14:49 +02:00
committed by Benoit Marty
parent c612c80b1d
commit 01681e9fff
19 changed files with 122 additions and 84 deletions
@@ -193,10 +193,6 @@ class PreferencesFlowNode(
backstack.push(NavTarget.UserProfile(matrixUser))
}
override fun navigateToBlockedUsers() {
backstack.push(NavTarget.BlockedUsers)
}
override fun startSignOutFlow() {
backstack.push(NavTarget.SignOut)
}
@@ -236,7 +232,12 @@ class PreferencesFlowNode(
createNode<LabsNode>(buildContext, listOf(callback))
}
NavTarget.ModerationAndSafety -> {
createNode<ModerationAndSafetyNode>(buildContext)
val callback = object : ModerationAndSafetyNode.Callback {
override fun navigateToBlockedUsers() {
backstack.push(NavTarget.BlockedUsers)
}
}
createNode<ModerationAndSafetyNode>(buildContext, listOf(callback))
}
NavTarget.LocationSettings -> {
createNode<LocationSettingsNode>(buildContext)
@@ -16,7 +16,6 @@ interface PreferencesAccountCallback {
fun navigateToNotificationSettings()
fun navigateToLinkNewDevice()
fun navigateToUserProfile(matrixUser: MatrixUser)
fun navigateToBlockedUsers()
fun startSignOutFlow()
fun startAccountDeactivationFlow()
}
@@ -27,8 +27,6 @@ import io.element.android.libraries.featureflag.api.FeatureFlags
import io.element.android.libraries.indicator.api.IndicatorService
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.matrix.api.verification.SessionVerificationService
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
@Inject
class PreferencesAccountPresenter(
@@ -67,12 +65,6 @@ class PreferencesAccountPresenter(
canDeactivateAccount = matrixClient.canDeactivateAccount()
}
val numberOfBlockedUsers by produceState(initialValue = 0) {
matrixClient.ignoredUsersFlow
.onEach { value = it.size }
.launchIn(this)
}
val directLogoutState = directLogoutPresenter.present()
val accountManagementUrl by produceState<String?>(initialValue = null) {
@@ -91,7 +83,6 @@ class PreferencesAccountPresenter(
canReportBug = canReportBug,
showLinkNewDevice = showLinkNewDevice,
canDeactivateAccount = canDeactivateAccount,
numberOfBlockedUsers = numberOfBlockedUsers,
directLogoutState = directLogoutState,
snackbarMessage = snackbarMessage,
)
@@ -19,9 +19,6 @@ data class PreferencesAccountState(
val canReportBug: Boolean,
val showLinkNewDevice: Boolean,
val canDeactivateAccount: Boolean,
val numberOfBlockedUsers: Int,
val directLogoutState: DirectLogoutState,
val snackbarMessage: SnackbarMessage?,
) {
val showBlockedUsersItem = numberOfBlockedUsers > 0
}
)
@@ -26,7 +26,6 @@ open class PreferencesAccountStatePreviewParam : PreviewParameterProvider<Prefer
canReportBug = true,
showLinkNewDevice = true,
canDeactivateAccount = false,
numberOfBlockedUsers = 3,
),
aPreferencesAccountState(
myUser = aMatrixUser(displayName = null),
@@ -40,7 +39,6 @@ open class PreferencesAccountStatePreviewParam : PreviewParameterProvider<Prefer
),
aPreferencesAccountState(
canReportBug = true,
numberOfBlockedUsers = 3,
snackbarMessage = SnackbarMessage(CommonStrings.common_verification_complete),
),
aPreferencesAccountState(
@@ -58,7 +56,6 @@ fun aPreferencesAccountState(
canReportBug: Boolean = false,
showLinkNewDevice: Boolean = false,
canDeactivateAccount: Boolean = false,
numberOfBlockedUsers: Int = 0,
directLogoutState: DirectLogoutState = aDirectLogoutState(),
snackbarMessage: SnackbarMessage? = null,
) = PreferencesAccountState(
@@ -69,7 +66,6 @@ fun aPreferencesAccountState(
canReportBug = canReportBug,
showLinkNewDevice = showLinkNewDevice,
canDeactivateAccount = canDeactivateAccount,
numberOfBlockedUsers = numberOfBlockedUsers,
directLogoutState = directLogoutState,
snackbarMessage = snackbarMessage,
)
@@ -35,7 +35,6 @@ fun PreferencesAccountView(
onOpenRageShake: () -> Unit,
onModerationAndSafetyClick: () -> Unit,
onOpenNotificationSettings: () -> Unit,
onOpenBlockedUsers: () -> Unit,
onSignOutClick: () -> Unit,
onDeactivateClick: () -> Unit,
modifier: Modifier = Modifier,
@@ -46,7 +45,6 @@ fun PreferencesAccountView(
state = state,
onManageAccountClick = onManageAccountClick,
onLinkNewDeviceClick = onLinkNewDeviceClick,
onOpenBlockedUsers = onOpenBlockedUsers,
)
OtherSettingsSection(
state = state,
@@ -69,7 +67,6 @@ private fun ManageAccountSection(
state: PreferencesAccountState,
onManageAccountClick: (url: String) -> Unit,
onLinkNewDeviceClick: () -> Unit,
onOpenBlockedUsers: () -> Unit,
) {
PreferenceCategory(
title = stringResource(CommonStrings.common_account_settings),
@@ -90,14 +87,6 @@ private fun ManageAccountSection(
onClick = onLinkNewDeviceClick,
)
}
if (state.showBlockedUsersItem) {
ListItem(
content = { Text(stringResource(id = CommonStrings.common_blocked_users)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())),
onClick = onOpenBlockedUsers,
trailingContent = ListItemContent.Text(state.numberOfBlockedUsers.toString()),
)
}
}
}
@@ -173,9 +162,7 @@ internal fun PreferencesAccountViewPreview(@PreviewParameter(PreferencesAccountS
onManageAccountClick = {},
onLinkNewDeviceClick = {},
onOpenNotificationSettings = {},
onOpenBlockedUsers = {},
onSignOutClick = {},
onDeactivateClick = {},
)
}
@@ -15,6 +15,7 @@ import com.bumble.appyx.core.plugin.Plugin
import dev.zacsweers.metro.Assisted
import dev.zacsweers.metro.AssistedInject
import io.element.android.annotations.ContributesNode
import io.element.android.libraries.architecture.callback
import io.element.android.libraries.di.SessionScope
@ContributesNode(SessionScope::class)
@@ -24,6 +25,12 @@ class ModerationAndSafetyNode(
@Assisted plugins: List<Plugin>,
private val presenter: ModerationAndSafetyPresenter,
) : Node(buildContext, plugins = plugins) {
interface Callback : Plugin {
fun navigateToBlockedUsers()
}
private val callback: Callback = callback()
@Composable
override fun View(modifier: Modifier) {
val state = presenter.present()
@@ -31,6 +38,7 @@ class ModerationAndSafetyNode(
state = state,
modifier = modifier,
onBackClick = ::navigateUp,
onOpenBlockedUsers = callback::navigateToBlockedUsers,
)
}
}
@@ -10,16 +10,21 @@ package io.element.android.features.preferences.impl.moderation
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.produceState
import androidx.compose.runtime.remember
import dev.zacsweers.metro.Inject
import io.element.android.libraries.architecture.Presenter
import io.element.android.libraries.di.annotations.SessionCoroutineScope
import io.element.android.libraries.matrix.api.MatrixClient
import io.element.android.libraries.preferences.api.store.SessionPreferencesStore
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
@Inject
class ModerationAndSafetyPresenter(
private val matrixClient: MatrixClient,
private val sessionPreferencesStore: SessionPreferencesStore,
private val mediaPreviewConfigStateStore: MediaPreviewConfigStateStore,
@SessionCoroutineScope
@@ -33,6 +38,12 @@ class ModerationAndSafetyPresenter(
val mediaPreviewConfigState = mediaPreviewConfigStateStore.state()
val numberOfBlockedUsers by produceState(initialValue = 0) {
matrixClient.ignoredUsersFlow
.onEach { value = it.size }
.launchIn(this)
}
fun handleEvent(event: ModerationAndSafetyEvent) {
when (event) {
is ModerationAndSafetyEvent.SetSharePresenceEnabled -> sessionCoroutineScope.launch {
@@ -46,6 +57,7 @@ class ModerationAndSafetyPresenter(
return ModerationAndSafetyState(
isSharePresenceEnabled = isSharePresenceEnabled,
mediaPreviewConfigState = mediaPreviewConfigState,
numberOfBlockedUsers = numberOfBlockedUsers,
eventSink = ::handleEvent,
)
}
@@ -10,5 +10,8 @@ package io.element.android.features.preferences.impl.moderation
data class ModerationAndSafetyState(
val isSharePresenceEnabled: Boolean,
val mediaPreviewConfigState: MediaPreviewConfigState,
val numberOfBlockedUsers: Int,
val eventSink: (ModerationAndSafetyEvent) -> Unit
)
) {
val showBlockedUsersItem = numberOfBlockedUsers > 0
}
@@ -20,6 +20,7 @@ open class ModerationAndSafetyStatePreviewParam : PreviewParameterProvider<Moder
aModerationAndSafetyState(timelineMediaPreviewValue = MediaPreviewValue.Off),
aModerationAndSafetyState(setHideInviteAvatarsAction = AsyncAction.Loading),
aModerationAndSafetyState(setTimelineMediaPreviewAction = AsyncAction.Loading),
aModerationAndSafetyState(numberOfBlockedUsers = 3),
)
}
@@ -29,6 +30,7 @@ fun aModerationAndSafetyState(
timelineMediaPreviewValue: MediaPreviewValue = MediaPreviewValue.On,
setTimelineMediaPreviewAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
setHideInviteAvatarsAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
numberOfBlockedUsers: Int = 0,
eventSink: (ModerationAndSafetyEvent) -> Unit = {},
) = ModerationAndSafetyState(
isSharePresenceEnabled = isSharePresenceEnabled,
@@ -38,5 +40,6 @@ fun aModerationAndSafetyState(
setTimelineMediaPreviewAction = setTimelineMediaPreviewAction,
setHideInviteAvatarsAction = setHideInviteAvatarsAction
),
numberOfBlockedUsers = numberOfBlockedUsers,
eventSink = eventSink
)
@@ -14,6 +14,7 @@ import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.PreviewParameter
import io.element.android.compound.tokens.generated.CompoundIcons
import io.element.android.features.preferences.impl.R
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
import io.element.android.libraries.designsystem.components.list.ListItemContent
@@ -24,6 +25,7 @@ import io.element.android.libraries.designsystem.preview.ElementPreviewBlack
import io.element.android.libraries.designsystem.preview.ElementPreviewDark
import io.element.android.libraries.designsystem.preview.ElementPreviewLight
import io.element.android.libraries.designsystem.preview.PreviewWithLargeHeight
import io.element.android.libraries.designsystem.theme.components.IconSource
import io.element.android.libraries.designsystem.theme.components.ListItem
import io.element.android.libraries.designsystem.theme.components.ListSectionHeader
import io.element.android.libraries.designsystem.theme.components.ListSupportingText
@@ -43,6 +45,7 @@ import io.element.android.libraries.ui.strings.CommonStrings
fun ModerationAndSafetyView(
state: ModerationAndSafetyState,
onBackClick: () -> Unit,
onOpenBlockedUsers: () -> Unit,
modifier: Modifier = Modifier,
) {
val snackbarDispatcher = LocalSnackbarDispatcher.current
@@ -63,6 +66,29 @@ fun ModerationAndSafetyView(
PresenceSection(state)
InvitesSection(state)
MediaSection(state)
if (state.showBlockedUsersItem) {
BlockedUsersSection(
numberOfBlockedUsers = state.numberOfBlockedUsers,
onOpenBlockedUsers = onOpenBlockedUsers,
)
}
}
}
@Composable
private fun BlockedUsersSection(
numberOfBlockedUsers: Int,
onOpenBlockedUsers: () -> Unit,
) {
PreferenceCategory(
title = stringResource(R.string.screen_moderation_and_safety_other_users_heading),
) {
ListItem(
content = { Text(stringResource(id = CommonStrings.common_blocked_users)) },
leadingContent = ListItemContent.Icon(IconSource.Vector(CompoundIcons.Block())),
onClick = onOpenBlockedUsers,
trailingContent = ListItemContent.Text(numberOfBlockedUsers.toString()),
)
}
}
@@ -175,6 +201,7 @@ internal fun ModerationAndSafetyViewBlackPreview(@PreviewParameter(ModerationAnd
private fun ContentToPreview(state: ModerationAndSafetyState) {
ModerationAndSafetyView(
state = state,
onBackClick = { }
onBackClick = { },
onOpenBlockedUsers = { },
)
}
@@ -89,7 +89,6 @@ class PreferencesRootNode(
onManageAccountClick = { onManageAccountClick(activity, it, isDark) },
onOpenNotificationSettings = callback::navigateToNotificationSettings,
onEditProfileClick = callback::navigateToUserProfile,
onOpenBlockedUsers = callback::navigateToBlockedUsers,
onSignOutClick = {
if (state.preferencesAccountState.directLogoutState.canDoDirectSignOut) {
state.preferencesAccountState.directLogoutState.eventSink(DirectLogoutEvent.Logout(ignoreSdkError = false))
@@ -93,7 +93,6 @@ fun PreferencesRootView(
onOpenRageShake: () -> Unit,
onModerationAndSafetyClick: () -> Unit,
onOpenNotificationSettings: () -> Unit,
onOpenBlockedUsers: () -> Unit,
onSignOutClick: () -> Unit,
onDeactivateClick: () -> Unit,
modifier: Modifier = Modifier,
@@ -140,7 +139,6 @@ fun PreferencesRootView(
onOpenRageShake = onOpenRageShake,
onModerationAndSafetyClick = onModerationAndSafetyClick,
onOpenNotificationSettings = onOpenNotificationSettings,
onOpenBlockedUsers = onOpenBlockedUsers,
onSignOutClick = onSignOutClick,
onDeactivateClick = onDeactivateClick,
)
@@ -441,7 +439,6 @@ private fun ContentToPreview(state: PreferencesRootState) {
onOpenRageShake = {},
onModerationAndSafetyClick = {},
onOpenNotificationSettings = {},
onOpenBlockedUsers = {},
onSignOutClick = {},
onDeactivateClick = {},
)
@@ -62,6 +62,7 @@
<string name="screen_labs_header_title">"Feeling experimental?"</string>
<string name="screen_labs_title">"Labs"</string>
<string name="screen_moderation_and_safety_invites_heading">"Invites"</string>
<string name="screen_moderation_and_safety_other_users_heading">"Other users"</string>
<string name="screen_moderation_and_safety_share_presence_heading">"Your appearance"</string>
<string name="screen_notification_settings_additional_settings_section_title">"Additional settings"</string>
<string name="screen_notification_settings_call_ringtone_label">"Call ringtone"</string>
@@ -22,8 +22,6 @@ import io.element.android.libraries.matrix.api.oauth.AccountManagementAction
import io.element.android.libraries.matrix.api.user.MatrixUser
import io.element.android.libraries.matrix.test.AN_AVATAR_URL
import io.element.android.libraries.matrix.test.A_SESSION_ID
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.A_USER_NAME
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.matrix.test.verification.FakeSessionVerificationService
@@ -32,8 +30,6 @@ import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.lambda.lambdaRecorder
import io.element.android.tests.testutils.lambda.value
import io.element.android.tests.testutils.test
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.test.runTest
import org.junit.Rule
@@ -69,8 +65,6 @@ class PreferencesAccountPresenterTest {
assertThat(initialState.showSecureBackupBadge).isFalse()
assertThat(initialState.accountManagementUrl).isNull()
assertThat(initialState.showLinkNewDevice).isFalse()
assertThat(initialState.numberOfBlockedUsers).isEqualTo(0)
assertThat(initialState.showBlockedUsersItem).isFalse()
assertThat(initialState.directLogoutState).isEqualTo(aDirectLogoutState())
assertThat(initialState.snackbarMessage).isNull()
val loadedState = consumeItemsUntilPredicate { it.accountManagementUrl != null }.last()
@@ -98,21 +92,6 @@ class PreferencesAccountPresenterTest {
}
}
@Test
fun `present - number of blocked users`() = runTest {
createPreferencesAccountPresenter(
matrixClient = FakeMatrixClient(
canDeactivateAccountResult = { true },
accountManagementUrlResult = { Result.success("") },
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID, A_USER_ID_2)),
),
).test {
val state = consumeItemsUntilPredicate { it.numberOfBlockedUsers == 2 }.last()
assertThat(state.showBlockedUsersItem).isTrue()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - secure backup badge`() = runTest {
val indicatorService = FakeIndicatorService()
@@ -62,23 +62,6 @@ class PreferencesAccountViewTest : RobolectricTest() {
onNodeWithText(activity!!.getString(CommonStrings.common_link_new_device)).assertDoesNotExist()
}
@Test
fun `click on Blocked users invokes the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
setView(
aPreferencesAccountState(numberOfBlockedUsers = 1),
onOpenBlockedUsers = callback,
)
clickOn(CommonStrings.common_blocked_users)
}
}
@Test
fun `when numberOfBlockedUsers is 0, item is not shown`() = runAndroidComposeUiTest {
setView(aPreferencesAccountState(numberOfBlockedUsers = 0))
onNodeWithText(activity!!.getString(CommonStrings.common_blocked_users)).assertDoesNotExist()
}
@Test
fun `click on Notification invokes the expected callback`() = runAndroidComposeUiTest {
ensureCalledOnce { callback ->
@@ -177,7 +160,6 @@ private fun AndroidComposeUiTest<ComponentActivity>.setView(
onOpenRageShake: () -> Unit = EnsureNeverCalled(),
onModerationAndSafetyClick: () -> Unit = EnsureNeverCalled(),
onOpenNotificationSettings: () -> Unit = EnsureNeverCalled(),
onOpenBlockedUsers: () -> Unit = EnsureNeverCalled(),
onSignOutClick: () -> Unit = EnsureNeverCalled(),
onDeactivateClick: () -> Unit = EnsureNeverCalled(),
) {
@@ -190,7 +172,6 @@ private fun AndroidComposeUiTest<ComponentActivity>.setView(
onOpenRageShake = onOpenRageShake,
onModerationAndSafetyClick = onModerationAndSafetyClick,
onOpenNotificationSettings = onOpenNotificationSettings,
onOpenBlockedUsers = onOpenBlockedUsers,
onSignOutClick = onSignOutClick,
onDeactivateClick = onDeactivateClick,
)
@@ -10,10 +10,16 @@ package io.element.android.features.preferences.impl.moderation
import com.google.common.truth.Truth.assertThat
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.matrix.test.A_USER_ID
import io.element.android.libraries.matrix.test.A_USER_ID_2
import io.element.android.libraries.matrix.test.FakeMatrixClient
import io.element.android.libraries.preferences.test.InMemorySessionPreferencesStore
import io.element.android.tests.testutils.WarmUpRule
import io.element.android.tests.testutils.consumeItemsUntilPredicate
import io.element.android.tests.testutils.test
import kotlinx.collections.immutable.persistentListOf
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.test.runTest
import org.junit.Rule
import org.junit.Test
@@ -31,10 +37,25 @@ class ModerationAndSafetyPresenterTest {
assertThat(mediaPreviewConfigState.timelineMediaPreviewValue).isEqualTo(MediaPreviewValue.On)
assertThat(mediaPreviewConfigState.setHideInviteAvatarsAction).isEqualTo(AsyncAction.Uninitialized)
assertThat(mediaPreviewConfigState.setTimelineMediaPreviewAction).isEqualTo(AsyncAction.Uninitialized)
assertThat(numberOfBlockedUsers).isEqualTo(0)
assertThat(showBlockedUsersItem).isFalse()
}
}
}
@Test
fun `present - number of blocked users`() = runTest {
createModerationAndSafetyPresenter(
matrixClient = FakeMatrixClient(
ignoredUsersFlow = MutableStateFlow(persistentListOf(A_USER_ID, A_USER_ID_2)),
),
).test {
val state = consumeItemsUntilPredicate { it.numberOfBlockedUsers == 2 }.last()
assertThat(state.showBlockedUsersItem).isTrue()
cancelAndIgnoreRemainingEvents()
}
}
@Test
fun `present - share presence off on`() = runTest {
createModerationAndSafetyPresenter().test {
@@ -121,9 +142,11 @@ class ModerationAndSafetyPresenterTest {
}
private fun CoroutineScope.createModerationAndSafetyPresenter(
matrixClient: FakeMatrixClient = FakeMatrixClient(),
sessionPreferencesStore: InMemorySessionPreferencesStore = InMemorySessionPreferencesStore(),
mediaPreviewConfigStateStore: MediaPreviewConfigStateStore = FakeMediaPreviewConfigStateStore(),
) = ModerationAndSafetyPresenter(
matrixClient = matrixClient,
sessionPreferencesStore = sessionPreferencesStore,
mediaPreviewConfigStateStore = mediaPreviewConfigStateStore,
sessionCoroutineScope = this,
@@ -12,10 +12,16 @@ package io.element.android.features.preferences.impl.moderation
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.AndroidComposeUiTest
import androidx.compose.ui.test.ExperimentalTestApi
import androidx.compose.ui.test.hasClickAction
import androidx.compose.ui.test.hasText
import androidx.compose.ui.test.onNodeWithText
import androidx.compose.ui.test.performClick
import androidx.compose.ui.test.performScrollTo
import androidx.compose.ui.test.v2.runAndroidComposeUiTest
import io.element.android.features.preferences.impl.R
import io.element.android.libraries.architecture.AsyncAction
import io.element.android.libraries.matrix.api.media.MediaPreviewValue
import io.element.android.libraries.ui.strings.CommonStrings
import io.element.android.tests.testutils.EnsureNeverCalled
import io.element.android.tests.testutils.EventsRecorder
import io.element.android.tests.testutils.clickOn
@@ -138,16 +144,46 @@ class ModerationAndSafetyViewTest : RobolectricTest() {
clickOn(R.string.screen_advanced_settings_show_media_timeline_always_hide)
clickOn(R.string.screen_advanced_settings_show_media_timeline_private_rooms)
}
@Test
fun `click on Blocked users invokes the expected callback`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<ModerationAndSafetyEvent>(expectEvents = false)
ensureCalledOnce { callback ->
setModerationAndSafetyView(
state = aModerationAndSafetyState(
numberOfBlockedUsers = 1,
eventSink = eventsRecorder,
),
onOpenBlockedUsers = callback,
)
val text = activity!!.getString(CommonStrings.common_blocked_users)
onNode(hasText(text) and hasClickAction()).performScrollTo().performClick()
}
}
@Test
fun `when numberOfBlockedUsers is 0, Blocked users item is not shown`() = runAndroidComposeUiTest {
val eventsRecorder = EventsRecorder<ModerationAndSafetyEvent>(expectEvents = false)
setModerationAndSafetyView(
state = aModerationAndSafetyState(
numberOfBlockedUsers = 0,
eventSink = eventsRecorder,
),
)
onNodeWithText(activity!!.getString(CommonStrings.common_blocked_users)).assertDoesNotExist()
}
}
private fun AndroidComposeUiTest<ComponentActivity>.setModerationAndSafetyView(
state: ModerationAndSafetyState,
onBackClick: () -> Unit = EnsureNeverCalled(),
onOpenBlockedUsers: () -> Unit = EnsureNeverCalled(),
) {
setContent {
ModerationAndSafetyView(
state = state,
onBackClick = onBackClick,
onOpenBlockedUsers = onOpenBlockedUsers,
)
}
}
@@ -409,7 +409,6 @@ private fun AndroidComposeUiTest<ComponentActivity>.setView(
onOpenRageShake: () -> Unit = EnsureNeverCalled(),
onModerationAndSafetyClick: () -> Unit = EnsureNeverCalled(),
onOpenNotificationSettings: () -> Unit = EnsureNeverCalled(),
onOpenBlockedUsers: () -> Unit = EnsureNeverCalled(),
onSignOutClick: () -> Unit = EnsureNeverCalled(),
onDeactivateClick: () -> Unit = EnsureNeverCalled(),
) {
@@ -433,7 +432,6 @@ private fun AndroidComposeUiTest<ComponentActivity>.setView(
onOpenRageShake = onOpenRageShake,
onModerationAndSafetyClick = onModerationAndSafetyClick,
onOpenNotificationSettings = onOpenNotificationSettings,
onOpenBlockedUsers = onOpenBlockedUsers,
onSignOutClick = onSignOutClick,
onDeactivateClick = onDeactivateClick,
)