Remove confirmExitAction and use AsyncAction.ConfirmingCancellation instead.
This commit is contained in:
parent
278231f3d2
commit
eec632661f
15 changed files with 102 additions and 45 deletions
|
|
@ -383,7 +383,16 @@ class RoomDetailsFlowNode(
|
||||||
knockRequestsListEntryPoint.createNode(this, buildContext)
|
knockRequestsListEntryPoint.createNode(this, buildContext)
|
||||||
}
|
}
|
||||||
NavTarget.SecurityAndPrivacy -> {
|
NavTarget.SecurityAndPrivacy -> {
|
||||||
securityAndPrivacyEntryPoint.createNode(this, buildContext)
|
val callback = object : SecurityAndPrivacyEntryPoint.Callback {
|
||||||
|
override fun onDone() {
|
||||||
|
backstack.pop()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
securityAndPrivacyEntryPoint.createNode(
|
||||||
|
parentNode = this,
|
||||||
|
buildContext = buildContext,
|
||||||
|
callback = callback,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
is NavTarget.VerifyUser -> {
|
is NavTarget.VerifyUser -> {
|
||||||
val params = OutgoingVerificationEntryPoint.Params(
|
val params = OutgoingVerificationEntryPoint.Params(
|
||||||
|
|
|
||||||
|
|
@ -8,6 +8,19 @@
|
||||||
|
|
||||||
package io.element.android.features.securityandprivacy.api
|
package io.element.android.features.securityandprivacy.api
|
||||||
|
|
||||||
import io.element.android.libraries.architecture.SimpleFeatureEntryPoint
|
import com.bumble.appyx.core.modality.BuildContext
|
||||||
|
import com.bumble.appyx.core.node.Node
|
||||||
|
import com.bumble.appyx.core.plugin.Plugin
|
||||||
|
import io.element.android.libraries.architecture.FeatureEntryPoint
|
||||||
|
|
||||||
fun interface SecurityAndPrivacyEntryPoint : SimpleFeatureEntryPoint
|
fun interface SecurityAndPrivacyEntryPoint : FeatureEntryPoint {
|
||||||
|
interface Callback : Plugin {
|
||||||
|
fun onDone()
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createNode(
|
||||||
|
parentNode: Node,
|
||||||
|
buildContext: BuildContext,
|
||||||
|
callback: Callback,
|
||||||
|
): Node
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,11 @@ import io.element.android.libraries.di.RoomScope
|
||||||
|
|
||||||
@ContributesBinding(RoomScope::class)
|
@ContributesBinding(RoomScope::class)
|
||||||
class DefaultSecurityAndPrivacyEntryPoint : SecurityAndPrivacyEntryPoint {
|
class DefaultSecurityAndPrivacyEntryPoint : SecurityAndPrivacyEntryPoint {
|
||||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
override fun createNode(
|
||||||
return parentNode.createNode<SecurityAndPrivacyFlowNode>(buildContext)
|
parentNode: Node,
|
||||||
|
buildContext: BuildContext,
|
||||||
|
callback: SecurityAndPrivacyEntryPoint.Callback,
|
||||||
|
): Node {
|
||||||
|
return parentNode.createNode<SecurityAndPrivacyFlowNode>(buildContext, listOf(callback))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,10 +18,12 @@ import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import dev.zacsweers.metro.Assisted
|
import dev.zacsweers.metro.Assisted
|
||||||
import dev.zacsweers.metro.AssistedInject
|
import dev.zacsweers.metro.AssistedInject
|
||||||
import io.element.android.annotations.ContributesNode
|
import io.element.android.annotations.ContributesNode
|
||||||
|
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint
|
||||||
import io.element.android.features.securityandprivacy.impl.editroomaddress.EditRoomAddressNode
|
import io.element.android.features.securityandprivacy.impl.editroomaddress.EditRoomAddressNode
|
||||||
import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyNode
|
import io.element.android.features.securityandprivacy.impl.root.SecurityAndPrivacyNode
|
||||||
import io.element.android.libraries.architecture.BackstackView
|
import io.element.android.libraries.architecture.BackstackView
|
||||||
import io.element.android.libraries.architecture.BaseFlowNode
|
import io.element.android.libraries.architecture.BaseFlowNode
|
||||||
|
import io.element.android.libraries.architecture.callback
|
||||||
import io.element.android.libraries.architecture.createNode
|
import io.element.android.libraries.architecture.createNode
|
||||||
import io.element.android.libraries.di.RoomScope
|
import io.element.android.libraries.di.RoomScope
|
||||||
import kotlinx.parcelize.Parcelize
|
import kotlinx.parcelize.Parcelize
|
||||||
|
|
@ -47,7 +49,8 @@ class SecurityAndPrivacyFlowNode(
|
||||||
data object EditRoomAddress : NavTarget
|
data object EditRoomAddress : NavTarget
|
||||||
}
|
}
|
||||||
|
|
||||||
private val navigator = BackstackSecurityAndPrivacyNavigator(backstack)
|
private val callback: SecurityAndPrivacyEntryPoint.Callback = callback()
|
||||||
|
private val navigator = BackstackSecurityAndPrivacyNavigator(callback, backstack)
|
||||||
|
|
||||||
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
override fun resolve(navTarget: NavTarget, buildContext: BuildContext): Node {
|
||||||
return when (navTarget) {
|
return when (navTarget) {
|
||||||
|
|
|
||||||
|
|
@ -12,15 +12,22 @@ import com.bumble.appyx.core.plugin.Plugin
|
||||||
import com.bumble.appyx.navmodel.backstack.BackStack
|
import com.bumble.appyx.navmodel.backstack.BackStack
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.pop
|
import com.bumble.appyx.navmodel.backstack.operation.pop
|
||||||
import com.bumble.appyx.navmodel.backstack.operation.push
|
import com.bumble.appyx.navmodel.backstack.operation.push
|
||||||
|
import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntryPoint
|
||||||
|
|
||||||
interface SecurityAndPrivacyNavigator : Plugin {
|
interface SecurityAndPrivacyNavigator : Plugin {
|
||||||
|
fun onDone()
|
||||||
fun openEditRoomAddress()
|
fun openEditRoomAddress()
|
||||||
fun closeEditRoomAddress()
|
fun closeEditRoomAddress()
|
||||||
}
|
}
|
||||||
|
|
||||||
class BackstackSecurityAndPrivacyNavigator(
|
class BackstackSecurityAndPrivacyNavigator(
|
||||||
|
private val callback: SecurityAndPrivacyEntryPoint.Callback,
|
||||||
private val backStack: BackStack<SecurityAndPrivacyFlowNode.NavTarget>
|
private val backStack: BackStack<SecurityAndPrivacyFlowNode.NavTarget>
|
||||||
) : SecurityAndPrivacyNavigator {
|
) : SecurityAndPrivacyNavigator {
|
||||||
|
override fun onDone() {
|
||||||
|
callback.onDone()
|
||||||
|
}
|
||||||
|
|
||||||
override fun openEditRoomAddress() {
|
override fun openEditRoomAddress() {
|
||||||
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.EditRoomAddress)
|
backStack.push(SecurityAndPrivacyFlowNode.NavTarget.EditRoomAddress)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,7 +40,6 @@ class SecurityAndPrivacyNode(
|
||||||
val state by stateFlow.collectAsState()
|
val state by stateFlow.collectAsState()
|
||||||
SecurityAndPrivacyView(
|
SecurityAndPrivacyView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = this::navigateUp,
|
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -64,7 +64,6 @@ class SecurityAndPrivacyPresenter(
|
||||||
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock)
|
featureFlagService.isFeatureEnabledFlow(FeatureFlags.Knock)
|
||||||
}.collectAsState(false)
|
}.collectAsState(false)
|
||||||
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
val saveAction = remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
||||||
var confirmExitAction by remember { mutableStateOf<AsyncAction<Unit>>(AsyncAction.Uninitialized) }
|
|
||||||
val homeserverName = remember { matrixClient.userIdServerName() }
|
val homeserverName = remember { matrixClient.userIdServerName() }
|
||||||
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
val syncUpdateFlow = room.syncUpdateFlow.collectAsState()
|
||||||
val roomInfo by room.roomInfoFlow.collectAsState()
|
val roomInfo by room.roomInfoFlow.collectAsState()
|
||||||
|
|
@ -150,18 +149,24 @@ class SecurityAndPrivacyPresenter(
|
||||||
saveAction.value = AsyncAction.Uninitialized
|
saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
SecurityAndPrivacyEvents.Exit -> {
|
SecurityAndPrivacyEvents.Exit -> {
|
||||||
confirmExitAction = if (savedSettings == editedSettings || confirmExitAction.isConfirming()) {
|
saveAction.value = if (savedSettings == editedSettings || saveAction.value == AsyncAction.ConfirmingCancellation) {
|
||||||
AsyncAction.Success(Unit)
|
AsyncAction.Success(Unit)
|
||||||
} else {
|
} else {
|
||||||
AsyncAction.ConfirmingNoParams
|
AsyncAction.ConfirmingCancellation
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
SecurityAndPrivacyEvents.DismissExitConfirmation -> {
|
SecurityAndPrivacyEvents.DismissExitConfirmation -> {
|
||||||
confirmExitAction = AsyncAction.Uninitialized
|
saveAction.value = AsyncAction.Uninitialized
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(saveAction.value.isSuccess()) {
|
||||||
|
if (saveAction.value.isSuccess()) {
|
||||||
|
navigator.onDone()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val state = SecurityAndPrivacyState(
|
val state = SecurityAndPrivacyState(
|
||||||
savedSettings = savedSettings,
|
savedSettings = savedSettings,
|
||||||
editedSettings = editedSettings,
|
editedSettings = editedSettings,
|
||||||
|
|
@ -171,7 +176,6 @@ class SecurityAndPrivacyPresenter(
|
||||||
saveAction = saveAction.value,
|
saveAction = saveAction.value,
|
||||||
permissions = permissions,
|
permissions = permissions,
|
||||||
isSpace = roomInfo.isSpace,
|
isSpace = roomInfo.isSpace,
|
||||||
confirmExitAction = confirmExitAction,
|
|
||||||
eventSink = ::handleEvent,
|
eventSink = ::handleEvent,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,6 @@ data class SecurityAndPrivacyState(
|
||||||
val showEnableEncryptionConfirmation: Boolean,
|
val showEnableEncryptionConfirmation: Boolean,
|
||||||
val isKnockEnabled: Boolean,
|
val isKnockEnabled: Boolean,
|
||||||
val saveAction: AsyncAction<Unit>,
|
val saveAction: AsyncAction<Unit>,
|
||||||
val confirmExitAction: AsyncAction<Unit>,
|
|
||||||
val isSpace: Boolean,
|
val isSpace: Boolean,
|
||||||
private val permissions: SecurityAndPrivacyPermissions,
|
private val permissions: SecurityAndPrivacyPermissions,
|
||||||
val eventSink: (SecurityAndPrivacyEvents) -> Unit
|
val eventSink: (SecurityAndPrivacyEvents) -> Unit
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,7 @@ open class SecurityAndPrivacyStateProvider : PreviewParameterProvider<SecurityAn
|
||||||
isSpace = false,
|
isSpace = false,
|
||||||
),
|
),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
confirmExitAction = AsyncAction.ConfirmingCancellation,
|
saveAction = AsyncAction.ConfirmingCancellation,
|
||||||
isSpace = false,
|
isSpace = false,
|
||||||
),
|
),
|
||||||
aSecurityAndPrivacyState(
|
aSecurityAndPrivacyState(
|
||||||
|
|
@ -109,7 +109,6 @@ fun aSecurityAndPrivacyState(
|
||||||
homeserverName: String = "myserver.xyz",
|
homeserverName: String = "myserver.xyz",
|
||||||
showEncryptionConfirmation: Boolean = false,
|
showEncryptionConfirmation: Boolean = false,
|
||||||
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
saveAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
||||||
confirmExitAction: AsyncAction<Unit> = AsyncAction.Uninitialized,
|
|
||||||
permissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions(
|
permissions: SecurityAndPrivacyPermissions = SecurityAndPrivacyPermissions(
|
||||||
canChangeRoomAccess = true,
|
canChangeRoomAccess = true,
|
||||||
canChangeHistoryVisibility = true,
|
canChangeHistoryVisibility = true,
|
||||||
|
|
@ -125,7 +124,6 @@ fun aSecurityAndPrivacyState(
|
||||||
homeserverName = homeserverName,
|
homeserverName = homeserverName,
|
||||||
showEnableEncryptionConfirmation = showEncryptionConfirmation,
|
showEnableEncryptionConfirmation = showEncryptionConfirmation,
|
||||||
saveAction = saveAction,
|
saveAction = saveAction,
|
||||||
confirmExitAction = confirmExitAction,
|
|
||||||
isKnockEnabled = isKnockEnabled,
|
isKnockEnabled = isKnockEnabled,
|
||||||
permissions = permissions,
|
permissions = permissions,
|
||||||
isSpace = isSpace,
|
isSpace = isSpace,
|
||||||
|
|
|
||||||
|
|
@ -32,6 +32,7 @@ import androidx.compose.ui.unit.dp
|
||||||
import io.element.android.compound.theme.ElementTheme
|
import io.element.android.compound.theme.ElementTheme
|
||||||
import io.element.android.compound.tokens.generated.CompoundIcons
|
import io.element.android.compound.tokens.generated.CompoundIcons
|
||||||
import io.element.android.features.securityandprivacy.impl.R
|
import io.element.android.features.securityandprivacy.impl.R
|
||||||
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
import io.element.android.libraries.architecture.coverage.ExcludeFromCoverage
|
||||||
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
import io.element.android.libraries.designsystem.components.async.AsyncActionView
|
||||||
|
|
@ -56,7 +57,6 @@ import kotlinx.collections.immutable.ImmutableSet
|
||||||
@Composable
|
@Composable
|
||||||
fun SecurityAndPrivacyView(
|
fun SecurityAndPrivacyView(
|
||||||
state: SecurityAndPrivacyState,
|
state: SecurityAndPrivacyState,
|
||||||
onBackClick: () -> Unit,
|
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
) {
|
) {
|
||||||
BackHandler {
|
BackHandler {
|
||||||
|
|
@ -130,6 +130,16 @@ fun SecurityAndPrivacyView(
|
||||||
async = state.saveAction,
|
async = state.saveAction,
|
||||||
onSuccess = { },
|
onSuccess = { },
|
||||||
onErrorDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissSaveError) },
|
onErrorDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissSaveError) },
|
||||||
|
confirmationDialog = { confirming ->
|
||||||
|
when (confirming) {
|
||||||
|
is AsyncAction.ConfirmingCancellation ->
|
||||||
|
SaveChangesDialog(
|
||||||
|
onSaveClick = { state.eventSink(SecurityAndPrivacyEvents.Save) },
|
||||||
|
onDiscardClick = { state.eventSink(SecurityAndPrivacyEvents.Exit) },
|
||||||
|
onDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissExitConfirmation) }
|
||||||
|
)
|
||||||
|
}
|
||||||
|
},
|
||||||
errorMessage = { stringResource(CommonStrings.error_unknown) },
|
errorMessage = { stringResource(CommonStrings.error_unknown) },
|
||||||
progressDialog = {
|
progressDialog = {
|
||||||
AsyncActionViewDefaults.ProgressDialog(
|
AsyncActionViewDefaults.ProgressDialog(
|
||||||
|
|
@ -138,18 +148,6 @@ fun SecurityAndPrivacyView(
|
||||||
},
|
},
|
||||||
onRetry = { state.eventSink(SecurityAndPrivacyEvents.Save) },
|
onRetry = { state.eventSink(SecurityAndPrivacyEvents.Save) },
|
||||||
)
|
)
|
||||||
AsyncActionView(
|
|
||||||
async = state.confirmExitAction,
|
|
||||||
onSuccess = { onBackClick() },
|
|
||||||
onErrorDismiss = { },
|
|
||||||
confirmationDialog = {
|
|
||||||
SaveChangesDialog(
|
|
||||||
onSaveClick = { state.eventSink(SecurityAndPrivacyEvents.Save) },
|
|
||||||
onDiscardClick = { state.eventSink(SecurityAndPrivacyEvents.Exit) },
|
|
||||||
onDismiss = { state.eventSink(SecurityAndPrivacyEvents.DismissExitConfirmation) }
|
|
||||||
)
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalMaterial3Api::class)
|
@OptIn(ExperimentalMaterial3Api::class)
|
||||||
|
|
@ -426,6 +424,5 @@ internal fun SecurityAndPrivacyViewDarkPreview(@PreviewParameter(SecurityAndPriv
|
||||||
private fun ContentToPreview(state: SecurityAndPrivacyState) {
|
private fun ContentToPreview(state: SecurityAndPrivacyState) {
|
||||||
SecurityAndPrivacyView(
|
SecurityAndPrivacyView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = {},
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -11,9 +11,14 @@ package io.element.android.features.securityandprivacy.impl
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakeSecurityAndPrivacyNavigator(
|
class FakeSecurityAndPrivacyNavigator(
|
||||||
|
private val onDoneLambda: () -> Unit = { lambdaError() },
|
||||||
private val openEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
private val openEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
||||||
private val closeEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
private val closeEditRoomAddressLambda: () -> Unit = { lambdaError() },
|
||||||
) : SecurityAndPrivacyNavigator {
|
) : SecurityAndPrivacyNavigator {
|
||||||
|
override fun onDone() {
|
||||||
|
onDoneLambda()
|
||||||
|
}
|
||||||
|
|
||||||
override fun openEditRoomAddress() {
|
override fun openEditRoomAddress() {
|
||||||
openEditRoomAddressLambda()
|
openEditRoomAddressLambda()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -203,7 +203,7 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
@Test
|
@Test
|
||||||
fun `present - edit room address`() = runTest {
|
fun `present - edit room address`() = runTest {
|
||||||
val openEditRoomAddressLambda = lambdaRecorder<Unit> { }
|
val openEditRoomAddressLambda = lambdaRecorder<Unit> { }
|
||||||
val navigator = FakeSecurityAndPrivacyNavigator(openEditRoomAddressLambda)
|
val navigator = FakeSecurityAndPrivacyNavigator(openEditRoomAddressLambda = openEditRoomAddressLambda)
|
||||||
val presenter = createSecurityAndPrivacyPresenter(navigator = navigator)
|
val presenter = createSecurityAndPrivacyPresenter(navigator = navigator)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(1)
|
skipItems(1)
|
||||||
|
|
@ -231,7 +231,14 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
updateRoomVisibilityResult = updateRoomVisibilityLambda,
|
updateRoomVisibilityResult = updateRoomVisibilityLambda,
|
||||||
updateRoomHistoryVisibilityResult = updateRoomHistoryVisibilityLambda,
|
updateRoomHistoryVisibilityResult = updateRoomHistoryVisibilityLambda,
|
||||||
)
|
)
|
||||||
val presenter = createSecurityAndPrivacyPresenter(room = room)
|
val onDoneLambda = lambdaRecorder<Unit> { }
|
||||||
|
val navigator = FakeSecurityAndPrivacyNavigator(
|
||||||
|
onDoneLambda = onDoneLambda,
|
||||||
|
)
|
||||||
|
val presenter = createSecurityAndPrivacyPresenter(
|
||||||
|
room = room,
|
||||||
|
navigator = navigator,
|
||||||
|
)
|
||||||
presenter.test {
|
presenter.test {
|
||||||
skipItems(2)
|
skipItems(2)
|
||||||
with(awaitItem()) {
|
with(awaitItem()) {
|
||||||
|
|
@ -276,6 +283,7 @@ class SecurityAndPrivacyPresenterTest {
|
||||||
assert(updateJoinRuleLambda).isCalledOnce()
|
assert(updateJoinRuleLambda).isCalledOnce()
|
||||||
assert(updateRoomVisibilityLambda).isCalledOnce()
|
assert(updateRoomVisibilityLambda).isCalledOnce()
|
||||||
assert(updateRoomHistoryVisibilityLambda).isCalledOnce()
|
assert(updateRoomHistoryVisibilityLambda).isCalledOnce()
|
||||||
|
onDoneLambda.assertions().isCalledOnce()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -24,7 +24,6 @@ import io.element.android.features.securityandprivacy.impl.root.aSecurityAndPriv
|
||||||
import io.element.android.libraries.architecture.AsyncAction
|
import io.element.android.libraries.architecture.AsyncAction
|
||||||
import io.element.android.libraries.architecture.AsyncData
|
import io.element.android.libraries.architecture.AsyncData
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import io.element.android.tests.testutils.EnsureNeverCalled
|
|
||||||
import io.element.android.tests.testutils.EventsRecorder
|
import io.element.android.tests.testutils.EventsRecorder
|
||||||
import io.element.android.tests.testutils.clickOn
|
import io.element.android.tests.testutils.clickOn
|
||||||
import io.element.android.tests.testutils.pressBack
|
import io.element.android.tests.testutils.pressBack
|
||||||
|
|
@ -50,27 +49,27 @@ class SecurityAndPrivacyViewTest {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `confirm cancellation emits the expected event`() {
|
fun `discard cancellation emits the expected event`() {
|
||||||
val recorder = EventsRecorder<SecurityAndPrivacyEvents>()
|
val recorder = EventsRecorder<SecurityAndPrivacyEvents>()
|
||||||
val state = aSecurityAndPrivacyState(
|
val state = aSecurityAndPrivacyState(
|
||||||
confirmExitAction = AsyncAction.ConfirmingCancellation,
|
saveAction = AsyncAction.ConfirmingCancellation,
|
||||||
eventSink = recorder,
|
eventSink = recorder,
|
||||||
)
|
)
|
||||||
rule.setSecurityAndPrivacyView(state)
|
rule.setSecurityAndPrivacyView(state)
|
||||||
rule.clickOn(CommonStrings.action_ok)
|
rule.clickOn(CommonStrings.action_discard)
|
||||||
recorder.assertSingle(SecurityAndPrivacyEvents.Exit)
|
recorder.assertSingle(SecurityAndPrivacyEvents.Exit)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
fun `dismiss cancellation confirmation emits the expected event`() {
|
fun `save cancellation confirmation emits the expected event`() {
|
||||||
val recorder = EventsRecorder<SecurityAndPrivacyEvents>()
|
val recorder = EventsRecorder<SecurityAndPrivacyEvents>()
|
||||||
val state = aSecurityAndPrivacyState(
|
val state = aSecurityAndPrivacyState(
|
||||||
confirmExitAction = AsyncAction.ConfirmingCancellation,
|
saveAction = AsyncAction.ConfirmingCancellation,
|
||||||
eventSink = recorder,
|
eventSink = recorder,
|
||||||
)
|
)
|
||||||
rule.setSecurityAndPrivacyView(state)
|
rule.setSecurityAndPrivacyView(state)
|
||||||
rule.clickOn(CommonStrings.action_cancel)
|
rule.clickOn(CommonStrings.action_save, inDialog = true)
|
||||||
recorder.assertSingle(SecurityAndPrivacyEvents.DismissExitConfirmation)
|
recorder.assertSingle(SecurityAndPrivacyEvents.Save)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -185,12 +184,10 @@ private fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.setSecur
|
||||||
state: SecurityAndPrivacyState = aSecurityAndPrivacyState(
|
state: SecurityAndPrivacyState = aSecurityAndPrivacyState(
|
||||||
eventSink = EventsRecorder(expectEvents = false),
|
eventSink = EventsRecorder(expectEvents = false),
|
||||||
),
|
),
|
||||||
onBackClick: () -> Unit = EnsureNeverCalled(),
|
|
||||||
) {
|
) {
|
||||||
setContent {
|
setContent {
|
||||||
SecurityAndPrivacyView(
|
SecurityAndPrivacyView(
|
||||||
state = state,
|
state = state,
|
||||||
onBackClick = onBackClick,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,11 @@ import io.element.android.features.securityandprivacy.api.SecurityAndPrivacyEntr
|
||||||
import io.element.android.tests.testutils.lambda.lambdaError
|
import io.element.android.tests.testutils.lambda.lambdaError
|
||||||
|
|
||||||
class FakeSecurityAndPrivacyEntryPoint : SecurityAndPrivacyEntryPoint {
|
class FakeSecurityAndPrivacyEntryPoint : SecurityAndPrivacyEntryPoint {
|
||||||
override fun createNode(parentNode: Node, buildContext: BuildContext): Node {
|
override fun createNode(
|
||||||
|
parentNode: Node,
|
||||||
|
buildContext: BuildContext,
|
||||||
|
callback: SecurityAndPrivacyEntryPoint.Callback,
|
||||||
|
): Node {
|
||||||
lambdaError()
|
lambdaError()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,11 +10,14 @@ package io.element.android.tests.testutils
|
||||||
|
|
||||||
import androidx.activity.ComponentActivity
|
import androidx.activity.ComponentActivity
|
||||||
import androidx.annotation.StringRes
|
import androidx.annotation.StringRes
|
||||||
|
import androidx.compose.ui.test.SemanticsMatcher
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import androidx.compose.ui.test.hasAnyAncestor
|
||||||
import androidx.compose.ui.test.hasClickAction
|
import androidx.compose.ui.test.hasClickAction
|
||||||
import androidx.compose.ui.test.hasContentDescription
|
import androidx.compose.ui.test.hasContentDescription
|
||||||
import androidx.compose.ui.test.hasTestTag
|
import androidx.compose.ui.test.hasTestTag
|
||||||
import androidx.compose.ui.test.hasText
|
import androidx.compose.ui.test.hasText
|
||||||
|
import androidx.compose.ui.test.isDialog
|
||||||
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
|
||||||
import androidx.compose.ui.test.onFirst
|
import androidx.compose.ui.test.onFirst
|
||||||
import androidx.compose.ui.test.onNodeWithText
|
import androidx.compose.ui.test.onNodeWithText
|
||||||
|
|
@ -22,9 +25,16 @@ import androidx.compose.ui.test.performClick
|
||||||
import io.element.android.libraries.ui.strings.CommonStrings
|
import io.element.android.libraries.ui.strings.CommonStrings
|
||||||
import org.junit.rules.TestRule
|
import org.junit.rules.TestRule
|
||||||
|
|
||||||
fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.clickOn(@StringRes res: Int) {
|
val trueMatcher = SemanticsMatcher("true matcher") { true }
|
||||||
|
|
||||||
|
fun <R : TestRule> AndroidComposeTestRule<R, ComponentActivity>.clickOn(
|
||||||
|
@StringRes res: Int,
|
||||||
|
inDialog: Boolean = false,
|
||||||
|
) {
|
||||||
val text = activity.getString(res)
|
val text = activity.getString(res)
|
||||||
onNode(hasText(text) and hasClickAction())
|
onNode(
|
||||||
|
hasText(text) and hasClickAction() and if (inDialog) hasAnyAncestor(isDialog()) else trueMatcher
|
||||||
|
)
|
||||||
.performClick()
|
.performClick()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue