Updated on 2026-08-14
This commit is contained in:
parent
8af068b74b
commit
fc319f2b70
17 changed files with 518 additions and 68 deletions
|
|
@ -27,9 +27,9 @@ dependencies {
|
|||
implementation(projects.core.ui)
|
||||
|
||||
/* Project - Domain */
|
||||
api(projects.domain.account)
|
||||
api(projects.domain.pushNotificationPreferences)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.wallets)
|
||||
|
||||
/* AndroidX */
|
||||
implementation(deps.androidx.activity)
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.features.pushnotificationsettings.impl.model
|
||||
|
||||
import arrow.core.Either
|
||||
import com.arkivanov.decompose.router.slot.SlotNavigation
|
||||
import com.arkivanov.decompose.router.slot.activate
|
||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||
|
|
@ -13,13 +14,16 @@ import com.tangem.core.ui.extensions.TextReference
|
|||
import com.tangem.core.ui.extensions.resourceReference
|
||||
import com.tangem.core.ui.message.DialogMessage
|
||||
import com.tangem.core.ui.message.EventMessageAction
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pushnotificationpreferences.IsPushNotificationFirstActivationDoneUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.MarkPushNotificationFirstActivationDoneUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.ObserveWalletPushNotificationPreferencesUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.SetAllWalletPushNotificationPreferencesUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.UpdateWalletPushNotificationPreferenceUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationCategory
|
||||
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationPreference
|
||||
import com.tangem.domain.pushnotificationpreferences.models.WalletPushNotificationPreferences
|
||||
import com.tangem.domain.wallets.usecase.SetNotificationsEnabledUseCase
|
||||
import com.tangem.features.pushnotifications.api.analytics.PushNotificationAnalyticEvents
|
||||
import com.tangem.features.pushnotificationsettings.component.PushNotificationSettingsComponent
|
||||
import com.tangem.features.pushnotificationsettings.impl.R
|
||||
|
|
@ -30,9 +34,7 @@ import com.tangem.features.pushnotificationsettings.impl.entity.ToggleId
|
|||
import com.tangem.features.pushnotificationsettings.impl.entity.ToggleUM
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import com.tangem.utils.coroutines.JobHolder
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.coroutines.saveIn
|
||||
import com.tangem.utils.logging.TangemLogger
|
||||
import kotlinx.collections.immutable.PersistentList
|
||||
import kotlinx.collections.immutable.toPersistentList
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
|
|
@ -61,7 +63,10 @@ internal class PushNotificationSettingsModel @Inject constructor(
|
|||
private val updatePreference: UpdateWalletPushNotificationPreferenceUseCase,
|
||||
private val systemNotificationsStateProvider: SystemNotificationsStateProvider,
|
||||
private val settingsManager: SettingsManager,
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository,
|
||||
private val setAllPreferences: SetAllWalletPushNotificationPreferencesUseCase,
|
||||
private val setNotificationsEnabled: SetNotificationsEnabledUseCase,
|
||||
private val isFirstActivationDone: IsPushNotificationFirstActivationDoneUseCase,
|
||||
private val markFirstActivationDone: MarkPushNotificationFirstActivationDoneUseCase,
|
||||
) : Model() {
|
||||
|
||||
private val params: PushNotificationSettingsComponent.Params = paramsContainer.require()
|
||||
|
|
@ -123,12 +128,24 @@ internal class PushNotificationSettingsModel @Inject constructor(
|
|||
val tapped = pendingPermissionToggle
|
||||
pendingPermissionToggle = null
|
||||
modelScope.launch {
|
||||
osNotificationsEnabled.value = systemNotificationsStateProvider.areNotificationsEnabled()
|
||||
// Enable all three only on a real grant that actually turned notifications on: an
|
||||
// already-granted-but-OS-disabled wallet returns isGranted=true instantly, so also require
|
||||
// areNotificationsEnabled() before triggering the rule; a deny routes the user to settings.
|
||||
val isNotificationsEnabled = systemNotificationsStateProvider.areNotificationsEnabled()
|
||||
osNotificationsEnabled.value = isNotificationsEnabled
|
||||
analyticsEventHandler.send(PushNotificationAnalyticEvents.PermissionStatus(isAllowed = isGranted))
|
||||
if (isGranted && tapped != null) {
|
||||
applyOptimisticToggle(tapped, newValue = true)
|
||||
} else if (!isGranted) {
|
||||
|
||||
if (!isGranted || !isNotificationsEnabled) {
|
||||
markFirstActivationDone(userWalletId)
|
||||
showEnableNotificationsDialog()
|
||||
return@launch
|
||||
}
|
||||
|
||||
if (isFirstActivationDone(userWalletId)) {
|
||||
tapped?.let { applyOptimisticToggle(it, newValue = true) }
|
||||
} else if (enableAllCategories(initiatingToggle = tapped)) {
|
||||
// Fix the flag only after a successful enable, so a transient failure can retry.
|
||||
markFirstActivationDone(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -228,23 +245,54 @@ internal class PushNotificationSettingsModel @Inject constructor(
|
|||
}
|
||||
|
||||
private suspend fun writeToggle(spec: ToggleSpec, newValue: Boolean) {
|
||||
// TODO [REDACTED_TASK_KEY] figure out and maybe swap /tokens and /preferences further calls
|
||||
updatePreference(userWalletId, spec.category, newValue)
|
||||
.onRight {
|
||||
if (spec.category == PushNotificationCategory.TransactionAlerts) {
|
||||
// Best-effort token sync after the preference write already succeeded:
|
||||
// log a failure but don't surface it to the user or revert the toggle.
|
||||
runSuspendCatching { accountsCRUDRepository.syncTokens(userWalletId) }
|
||||
.onFailure { error ->
|
||||
TangemLogger.e(
|
||||
messageString = "Failed to sync tokens after enabling " +
|
||||
"transaction alerts for $userWalletId",
|
||||
throwable = error,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
.onLeft { revertOptimistic(spec, newValue) }
|
||||
if (spec.category == PushNotificationCategory.TransactionAlerts) {
|
||||
writeTransactionAlerts(spec, newValue)
|
||||
} else {
|
||||
updatePreference(userWalletId, spec.category, newValue).onLeft { revertOptimistic(spec, newValue) }
|
||||
}
|
||||
}
|
||||
|
||||
/** Tokens (address re-subscription) first, then preferences; on failure revert and undo the token subscription. */
|
||||
private suspend fun writeTransactionAlerts(spec: ToggleSpec, newValue: Boolean) {
|
||||
val tokensResult = setNotificationsEnabled(userWalletId, isEnabled = newValue)
|
||||
if (tokensResult is Either.Left) {
|
||||
revertOptimistic(spec, newValue)
|
||||
return
|
||||
}
|
||||
updatePreference(userWalletId, spec.category, newValue).onLeft {
|
||||
setNotificationsEnabled(userWalletId, isEnabled = !newValue)
|
||||
revertOptimistic(spec, newValue)
|
||||
}
|
||||
}
|
||||
|
||||
/** Enables all three categories at once (first-activation): tokens first, then preferences. True on full success. */
|
||||
private suspend fun enableAllCategories(initiatingToggle: ToggleSpec?): Boolean {
|
||||
val previous = cachedPrefs ?: return false
|
||||
loadState.value = LoadState.Content(
|
||||
previous.copy(
|
||||
transactionAlerts = previous.transactionAlerts.copy(isEnabled = true),
|
||||
offersUpdates = previous.offersUpdates.copy(isEnabled = true),
|
||||
priceAlerts = previous.priceAlerts.copy(isEnabled = true),
|
||||
),
|
||||
)
|
||||
val tokensResult = setNotificationsEnabled(userWalletId, isEnabled = true)
|
||||
if (tokensResult is Either.Left) {
|
||||
revertAll(previous, initiatingToggle)
|
||||
return false
|
||||
}
|
||||
return setAllPreferences(
|
||||
userWalletId = userWalletId,
|
||||
transactionAlerts = true,
|
||||
offersUpdates = true,
|
||||
priceAlerts = true,
|
||||
).fold(
|
||||
ifLeft = {
|
||||
setNotificationsEnabled(userWalletId, isEnabled = false)
|
||||
revertAll(previous, initiatingToggle)
|
||||
false
|
||||
},
|
||||
ifRight = { true },
|
||||
)
|
||||
}
|
||||
|
||||
private fun revertOptimistic(spec: ToggleSpec, newValue: Boolean) {
|
||||
|
|
@ -257,9 +305,18 @@ internal class PushNotificationSettingsModel @Inject constructor(
|
|||
state
|
||||
}
|
||||
}
|
||||
showWriteErrorMessage(toggleType = spec.id.analyticsValue)
|
||||
}
|
||||
|
||||
private fun revertAll(previous: WalletPushNotificationPreferences, initiatingToggle: ToggleSpec?) {
|
||||
loadState.update { state -> if (state is LoadState.Content) LoadState.Content(previous) else state }
|
||||
showWriteErrorMessage(toggleType = (initiatingToggle?.id ?: ToggleId.TransactionAlerts).analyticsValue)
|
||||
}
|
||||
|
||||
private fun showWriteErrorMessage(toggleType: String) {
|
||||
analyticsEventHandler.send(
|
||||
PushNotificationAnalyticEvents.NotificationSettingsErrorShown(
|
||||
toggleType = spec.id.analyticsValue,
|
||||
toggleType = toggleType,
|
||||
errorType = ERROR_TYPE_WRITE_FAILED,
|
||||
),
|
||||
)
|
||||
|
|
|
|||
|
|
@ -8,20 +8,23 @@ import com.tangem.core.decompose.model.MutableParamsContainer
|
|||
import com.tangem.core.decompose.ui.UiMessageSender
|
||||
import com.tangem.core.navigation.notifications.SystemNotificationsStateProvider
|
||||
import com.tangem.core.navigation.settings.SettingsManager
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.pushnotificationpreferences.IsPushNotificationFirstActivationDoneUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.MarkPushNotificationFirstActivationDoneUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.ObserveWalletPushNotificationPreferencesUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.SetAllWalletPushNotificationPreferencesUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.UpdateWalletPushNotificationPreferenceUseCase
|
||||
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationCategory
|
||||
import com.tangem.domain.pushnotificationpreferences.models.PushNotificationPreference
|
||||
import com.tangem.domain.pushnotificationpreferences.models.WalletPushNotificationPreferences
|
||||
import com.tangem.domain.wallets.usecase.SetNotificationsEnabledUseCase
|
||||
import com.tangem.features.pushnotificationsettings.component.PushNotificationSettingsComponent
|
||||
import com.tangem.features.pushnotificationsettings.impl.entity.AllowPushNotificationsBannerUM
|
||||
import com.tangem.features.pushnotificationsettings.impl.entity.PushNotificationSettingsUM
|
||||
import com.tangem.features.pushnotificationsettings.impl.entity.ToggleId
|
||||
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
|
||||
import io.mockk.coEvery
|
||||
import io.mockk.coVerify
|
||||
import io.mockk.coVerifyOrder
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import io.mockk.verify
|
||||
|
|
@ -31,25 +34,32 @@ import kotlinx.coroutines.test.advanceUntilIdle
|
|||
import kotlinx.coroutines.test.runTest
|
||||
import org.junit.jupiter.api.Test
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
@Suppress("LongParameterList", "LargeClass")
|
||||
class PushNotificationSettingsModelTest {
|
||||
|
||||
private val userWalletId = UserWalletId("0011223344556677")
|
||||
|
||||
private val observePreferences: ObserveWalletPushNotificationPreferencesUseCase = mockk()
|
||||
private val updatePreference: UpdateWalletPushNotificationPreferenceUseCase = mockk()
|
||||
private val setAllPreferences: SetAllWalletPushNotificationPreferencesUseCase = mockk()
|
||||
private val setNotificationsEnabled: SetNotificationsEnabledUseCase = mockk()
|
||||
private val isFirstActivationDone: IsPushNotificationFirstActivationDoneUseCase = mockk()
|
||||
private val markFirstActivationDone: MarkPushNotificationFirstActivationDoneUseCase = mockk(relaxed = true)
|
||||
private val systemNotificationsStateProvider: SystemNotificationsStateProvider = mockk()
|
||||
private val settingsManager: SettingsManager = mockk(relaxed = true)
|
||||
private val accountsCRUDRepository: AccountsCRUDRepository = mockk(relaxed = true)
|
||||
private val messageSender: UiMessageSender = mockk(relaxed = true)
|
||||
private val analyticsEventHandler: AnalyticsEventHandler = mockk(relaxed = true)
|
||||
|
||||
private fun model(
|
||||
osEnabled: Boolean = true,
|
||||
firstActivationDone: Boolean = true,
|
||||
preferencesFlow: MutableSharedFlow<WalletPushNotificationPreferences> = MutableSharedFlow(replay = 1),
|
||||
): PushNotificationSettingsModel {
|
||||
every { systemNotificationsStateProvider.areNotificationsEnabled() } returns osEnabled
|
||||
every { observePreferences(userWalletId) } returns preferencesFlow
|
||||
coEvery { isFirstActivationDone(userWalletId) } returns firstActivationDone
|
||||
coEvery { setNotificationsEnabled(any(), any()) } returns Either.Right(Unit)
|
||||
coEvery { setAllPreferences(any(), any(), any(), any()) } returns Either.Right(Unit)
|
||||
return PushNotificationSettingsModel(
|
||||
paramsContainer = MutableParamsContainer(PushNotificationSettingsComponent.Params(userWalletId)),
|
||||
dispatchers = TestingCoroutineDispatcherProvider(),
|
||||
|
|
@ -59,7 +69,10 @@ class PushNotificationSettingsModelTest {
|
|||
updatePreference = updatePreference,
|
||||
systemNotificationsStateProvider = systemNotificationsStateProvider,
|
||||
settingsManager = settingsManager,
|
||||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
setAllPreferences = setAllPreferences,
|
||||
setNotificationsEnabled = setNotificationsEnabled,
|
||||
isFirstActivationDone = isFirstActivationDone,
|
||||
markFirstActivationDone = markFirstActivationDone,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -79,6 +92,9 @@ class PushNotificationSettingsModelTest {
|
|||
fun `GIVEN observe throws WHEN model created THEN ui state becomes Error`() = runTest {
|
||||
every { observePreferences(userWalletId) } returns flow { throw IllegalStateException("boom") }
|
||||
every { systemNotificationsStateProvider.areNotificationsEnabled() } returns true
|
||||
coEvery { isFirstActivationDone(userWalletId) } returns true
|
||||
coEvery { setNotificationsEnabled(any(), any()) } returns Either.Right(Unit)
|
||||
coEvery { setAllPreferences(any(), any(), any(), any()) } returns Either.Right(Unit)
|
||||
|
||||
val model = PushNotificationSettingsModel(
|
||||
paramsContainer = MutableParamsContainer(PushNotificationSettingsComponent.Params(userWalletId)),
|
||||
|
|
@ -89,7 +105,10 @@ class PushNotificationSettingsModelTest {
|
|||
updatePreference = updatePreference,
|
||||
systemNotificationsStateProvider = systemNotificationsStateProvider,
|
||||
settingsManager = settingsManager,
|
||||
accountsCRUDRepository = accountsCRUDRepository,
|
||||
setAllPreferences = setAllPreferences,
|
||||
setNotificationsEnabled = setNotificationsEnabled,
|
||||
isFirstActivationDone = isFirstActivationDone,
|
||||
markFirstActivationDone = markFirstActivationDone,
|
||||
)
|
||||
advanceUntilIdle()
|
||||
|
||||
|
|
@ -197,6 +216,28 @@ class PushNotificationSettingsModelTest {
|
|||
assertThat(toggles.first { it.id == ToggleId.OffersUpdates }.isOn).isTrue()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN transaction alerts on WHEN written THEN tokens synced before preference`() = runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
coEvery {
|
||||
updatePreference(userWalletId, PushNotificationCategory.TransactionAlerts, true)
|
||||
} returns Either.Right(Unit)
|
||||
val model = model(osEnabled = true, firstActivationDone = true, preferencesFlow = flow)
|
||||
advanceUntilIdle()
|
||||
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.TransactionAlerts }
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Order matters: token re-subscription (which carries addresses) precedes the preferences write (spec §13.2).
|
||||
coVerifyOrder {
|
||||
setNotificationsEnabled(userWalletId, isEnabled = true)
|
||||
updatePreference(userWalletId, PushNotificationCategory.TransactionAlerts, true)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN OS disabled WHEN toggle ON THEN permission request is triggered`() = runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
|
|
@ -237,52 +278,195 @@ class PushNotificationSettingsModelTest {
|
|||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN Allow on a single tapped toggle THEN only that toggle is enabled`() = runTest {
|
||||
fun `GIVEN first activation not done WHEN Allow THEN all three categories enabled`() = runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
val model = model(osEnabled = false, firstActivationDone = false, preferencesFlow = flow)
|
||||
advanceUntilIdle()
|
||||
|
||||
// Tap a single toggle in not_determined state -> OS prompt fires.
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.OffersUpdates }
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
// User taps Allow.
|
||||
every { systemNotificationsStateProvider.areNotificationsEnabled() } returns true
|
||||
model.onPermissionResult(isGranted = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
// First-activation rule: all three enabled at once (not just the tapped one), token re-subscription, flag fixed.
|
||||
coVerify(exactly = 1) { setAllPreferences(userWalletId, true, true, true) }
|
||||
coVerify(exactly = 1) { setNotificationsEnabled(userWalletId, isEnabled = true) }
|
||||
coVerify(exactly = 1) { markFirstActivationDone(userWalletId) }
|
||||
coVerify(exactly = 0) { updatePreference(any(), any(), any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN first activation done WHEN Allow THEN only tapped toggle enabled`() = runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
coEvery {
|
||||
updatePreference(userWalletId, PushNotificationCategory.OffersUpdates, true)
|
||||
} returns Either.Right(Unit)
|
||||
|
||||
val model = model(osEnabled = false, preferencesFlow = flow)
|
||||
val model = model(osEnabled = false, firstActivationDone = true, preferencesFlow = flow)
|
||||
advanceUntilIdle()
|
||||
|
||||
val offers = (model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.OffersUpdates }
|
||||
offers.onCheckedChange(true)
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
// OS prompt fires; user taps Allow.
|
||||
every { systemNotificationsStateProvider.areNotificationsEnabled() } returns true
|
||||
model.onPermissionResult(isGranted = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 1) {
|
||||
updatePreference(userWalletId, PushNotificationCategory.OffersUpdates, true)
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
updatePreference(userWalletId, PushNotificationCategory.TransactionAlerts, any())
|
||||
}
|
||||
coVerify(exactly = 0) {
|
||||
updatePreference(userWalletId, PushNotificationCategory.PriceAlerts, any())
|
||||
}
|
||||
coVerify(exactly = 1) { updatePreference(userWalletId, PushNotificationCategory.OffersUpdates, true) }
|
||||
// Selective: exactly one preference write (the tapped one), no bulk enable, and the flag is already set.
|
||||
coVerify(exactly = 1) { updatePreference(any(), any(), any()) }
|
||||
coVerify(exactly = 0) { setAllPreferences(any(), any(), any(), any()) }
|
||||
coVerify(exactly = 0) { markFirstActivationDone(userWalletId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN Deny THEN Enable Notifications dialog is shown and no PUT`() = runTest {
|
||||
fun `GIVEN permission granted but notifications disabled WHEN result THEN no all-three and settings dialog`() =
|
||||
runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
val model = model(osEnabled = false, firstActivationDone = false, preferencesFlow = flow)
|
||||
advanceUntilIdle()
|
||||
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.OffersUpdates }
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
// POST_NOTIFICATIONS already granted (launcher returns true) but notifications stay OFF at OS level.
|
||||
model.onPermissionResult(isGranted = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { setAllPreferences(any(), any(), any(), any()) }
|
||||
coVerify(exactly = 0) { updatePreference(any(), any(), any()) }
|
||||
coVerify(exactly = 1) { messageSender.send(any()) }
|
||||
coVerify(exactly = 1) { markFirstActivationDone(userWalletId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN transaction alerts on AND tokens write fails WHEN written THEN preference not written and reverted`() =
|
||||
runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
val model = model(osEnabled = true, firstActivationDone = true, preferencesFlow = flow)
|
||||
// Registered AFTER model() so this specific stub wins over model()'s any()-> Right default.
|
||||
coEvery {
|
||||
setNotificationsEnabled(userWalletId, isEnabled = true)
|
||||
} returns Either.Left(RuntimeException("net"))
|
||||
advanceUntilIdle()
|
||||
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.TransactionAlerts }
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { updatePreference(userWalletId, PushNotificationCategory.TransactionAlerts, any()) }
|
||||
coVerify(atLeast = 1) { messageSender.send(any()) }
|
||||
val tx = (model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.TransactionAlerts }
|
||||
assertThat(tx.isOn).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN transaction alerts on AND preference write fails WHEN written THEN token subscription undone`() =
|
||||
runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
coEvery {
|
||||
updatePreference(userWalletId, PushNotificationCategory.TransactionAlerts, true)
|
||||
} returns Either.Left(RuntimeException("net"))
|
||||
val model = model(osEnabled = true, firstActivationDone = true, preferencesFlow = flow)
|
||||
advanceUntilIdle()
|
||||
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.TransactionAlerts }
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
|
||||
// tokens on -> preferences fail -> compensating tokens off; toggle reverted.
|
||||
coVerifyOrder {
|
||||
setNotificationsEnabled(userWalletId, isEnabled = true)
|
||||
updatePreference(userWalletId, PushNotificationCategory.TransactionAlerts, true)
|
||||
setNotificationsEnabled(userWalletId, isEnabled = false)
|
||||
}
|
||||
val tx = (model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.TransactionAlerts }
|
||||
assertThat(tx.isOn).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN first activation AND tokens write fails WHEN Allow THEN reverted and flag not marked`() = runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
val model = model(osEnabled = false, preferencesFlow = flow)
|
||||
val model = model(osEnabled = false, firstActivationDone = false, preferencesFlow = flow)
|
||||
// Registered AFTER model() so this specific stub wins over model()'s any()-> Right default.
|
||||
coEvery { setNotificationsEnabled(userWalletId, isEnabled = true) } returns Either.Left(RuntimeException("net"))
|
||||
advanceUntilIdle()
|
||||
|
||||
val offers = (model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.OffersUpdates }
|
||||
offers.onCheckedChange(true)
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
every { systemNotificationsStateProvider.areNotificationsEnabled() } returns true
|
||||
model.onPermissionResult(isGranted = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 0) { setAllPreferences(any(), any(), any(), any()) }
|
||||
coVerify(exactly = 0) { markFirstActivationDone(userWalletId) }
|
||||
coVerify(atLeast = 1) { messageSender.send(any()) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `GIVEN first activation AND preferences write fails WHEN Allow THEN tokens undone and flag not marked`() =
|
||||
runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
val model = model(osEnabled = false, firstActivationDone = false, preferencesFlow = flow)
|
||||
// Registered AFTER model() so this specific stub wins over model()'s any()-> Right default.
|
||||
coEvery {
|
||||
setAllPreferences(userWalletId, true, true, true)
|
||||
} returns Either.Left(RuntimeException("net"))
|
||||
advanceUntilIdle()
|
||||
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.OffersUpdates }
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
every { systemNotificationsStateProvider.areNotificationsEnabled() } returns true
|
||||
model.onPermissionResult(isGranted = true)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerifyOrder {
|
||||
setNotificationsEnabled(userWalletId, isEnabled = true)
|
||||
setAllPreferences(userWalletId, true, true, true)
|
||||
setNotificationsEnabled(userWalletId, isEnabled = false)
|
||||
}
|
||||
coVerify(exactly = 0) { markFirstActivationDone(userWalletId) }
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `WHEN Deny THEN dialog shown AND flag marked AND no writes`() = runTest {
|
||||
val flow = MutableSharedFlow<WalletPushNotificationPreferences>(replay = 1)
|
||||
flow.tryEmit(allFalse())
|
||||
val model = model(osEnabled = false, firstActivationDone = false, preferencesFlow = flow)
|
||||
advanceUntilIdle()
|
||||
|
||||
(model.uiState.value as PushNotificationSettingsUM.Content)
|
||||
.toggles.first { it.id == ToggleId.OffersUpdates }
|
||||
.onCheckedChange(true)
|
||||
advanceUntilIdle()
|
||||
model.onPermissionResult(isGranted = false)
|
||||
advanceUntilIdle()
|
||||
|
||||
coVerify(exactly = 1) { messageSender.send(any()) }
|
||||
coVerify(exactly = 1) { markFirstActivationDone(userWalletId) }
|
||||
coVerify(exactly = 0) { updatePreference(any(), any(), any()) }
|
||||
coVerify(exactly = 0) { setAllPreferences(any(), any(), any(), any()) }
|
||||
}
|
||||
|
||||
private fun allFalse() = WalletPushNotificationPreferences(
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue