From fc6767346cb7fb44ff3722a13e8e64fffebc4cdd Mon Sep 17 00:00:00 2001 From: Tangem Date: Fri, 17 Apr 2026 11:50:56 +0300 Subject: [PATCH] Updated on 2026-08-14 --- ...ializeWithCryptoCurrencyTransformerTest.kt | 5 +- .../SetBalanceLoadingTransformerTest.kt | 5 +- .../transformer/SetBalanceTransformerTest.kt | 5 +- .../SetTopBarTitleTransformerTest.kt | 3 +- .../ToggleBalanceTypeTransformerTest.kt | 5 +- .../UpdateNotificationsTransformerTest.kt | 540 ++++++++++++++++++ ...pdateStakingNotificationTransformerTest.kt | 137 +++++ .../UpdateTopBarMenuTransformerTest.kt | 3 +- 8 files changed, 693 insertions(+), 10 deletions(-) create mode 100644 features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateNotificationsTransformerTest.kt create mode 100644 features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateStakingNotificationTransformerTest.kt diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/InitializeWithCryptoCurrencyTransformerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/InitializeWithCryptoCurrencyTransformerTest.kt index 8c0623c3e8..52acfdab97 100644 --- a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/InitializeWithCryptoCurrencyTransformerTest.kt +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/InitializeWithCryptoCurrencyTransformerTest.kt @@ -102,7 +102,7 @@ class InitializeWithCryptoCurrencyTransformerTest { assertThat(result.topAppBarUM.menuItems).isEqualTo(state.topAppBarUM.menuItems) assertThat(result.balanceBlockUM.actionButtons).isEqualTo(state.balanceBlockUM.actionButtons) assertThat(result.balanceBlockUM.tokenBalanceTypeUM).isEqualTo(state.balanceBlockUM.tokenBalanceTypeUM) - assertThat(result.stakingBlocksState).isEqualTo(state.stakingBlocksState) + assertThat(result.earnBlockState).isEqualTo(state.earnBlockState) assertThat(result.pullToRefreshConfig).isSameInstanceAs(state.pullToRefreshConfig) assertThat(result.isBalanceHidden).isEqualTo(state.isBalanceHidden) assertThat(result.isMarketPriceAvailable).isEqualTo(state.isMarketPriceAvailable) @@ -120,8 +120,9 @@ class InitializeWithCryptoCurrencyTransformerTest { tokenBalanceTypeUM = TokenBalanceTypeUM.Single, currencyIconState = mockk(relaxed = true), ), + notifications = persistentListOf(), marketPriceBlockState = mockk(relaxed = true), - stakingBlocksState = null, + earnBlockState = null, pullToRefreshConfig = mockk(relaxed = true), isBalanceHidden = false, isMarketPriceAvailable = false, diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetBalanceLoadingTransformerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetBalanceLoadingTransformerTest.kt index 1d7b67394f..8e8a26f034 100644 --- a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetBalanceLoadingTransformerTest.kt +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetBalanceLoadingTransformerTest.kt @@ -114,7 +114,7 @@ class SetBalanceLoadingTransformerTest { // THEN assertThat(result.topAppBarUM).isSameInstanceAs(state.topAppBarUM) assertThat(result.marketPriceBlockState).isSameInstanceAs(state.marketPriceBlockState) - assertThat(result.stakingBlocksState).isEqualTo(state.stakingBlocksState) + assertThat(result.earnBlockState).isEqualTo(state.earnBlockState) assertThat(result.pullToRefreshConfig).isSameInstanceAs(state.pullToRefreshConfig) assertThat(result.isBalanceHidden).isEqualTo(state.isBalanceHidden) assertThat(result.isMarketPriceAvailable).isEqualTo(state.isMarketPriceAvailable) @@ -134,8 +134,9 @@ class SetBalanceLoadingTransformerTest { tokenBalanceTypeUM = TokenBalanceTypeUM.Single, currencyIconState = CurrencyIconState.Loading, ), + notifications = persistentListOf(), marketPriceBlockState = mockk(relaxed = true), - stakingBlocksState = null, + earnBlockState = null, pullToRefreshConfig = mockk(relaxed = true), isBalanceHidden = false, isMarketPriceAvailable = false, diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetBalanceTransformerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetBalanceTransformerTest.kt index 27a1733679..a78c3ed430 100644 --- a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetBalanceTransformerTest.kt +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetBalanceTransformerTest.kt @@ -390,7 +390,7 @@ class SetBalanceTransformerTest { // THEN assertThat(result.topAppBarUM).isSameInstanceAs(state.topAppBarUM) assertThat(result.marketPriceBlockState).isSameInstanceAs(state.marketPriceBlockState) - assertThat(result.stakingBlocksState).isEqualTo(state.stakingBlocksState) + assertThat(result.earnBlockState).isEqualTo(state.earnBlockState) assertThat(result.pullToRefreshConfig).isSameInstanceAs(state.pullToRefreshConfig) assertThat(result.isBalanceHidden).isEqualTo(state.isBalanceHidden) assertThat(result.isMarketPriceAvailable).isEqualTo(state.isMarketPriceAvailable) @@ -472,8 +472,9 @@ class SetBalanceTransformerTest { tokenBalanceTypeUM = TokenBalanceTypeUM.Single, currencyIconState = CurrencyIconState.Loading, ), + notifications = persistentListOf(), marketPriceBlockState = mockk(relaxed = true), - stakingBlocksState = null, + earnBlockState = null, pullToRefreshConfig = mockk(relaxed = true), isBalanceHidden = false, isMarketPriceAvailable = false, diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetTopBarTitleTransformerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetTopBarTitleTransformerTest.kt index 7f72295f3a..2e1d75e21c 100644 --- a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetTopBarTitleTransformerTest.kt +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/SetTopBarTitleTransformerTest.kt @@ -195,8 +195,9 @@ class SetTopBarTitleTransformerTest { menuItems = persistentListOf(), ), balanceBlockUM = mockk(relaxed = true), + notifications = persistentListOf(), marketPriceBlockState = mockk(relaxed = true), - stakingBlocksState = null, + earnBlockState = null, pullToRefreshConfig = mockk(relaxed = true), isBalanceHidden = false, isMarketPriceAvailable = false, diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/ToggleBalanceTypeTransformerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/ToggleBalanceTypeTransformerTest.kt index 2a288fa164..99be5d0fa3 100644 --- a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/ToggleBalanceTypeTransformerTest.kt +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/ToggleBalanceTypeTransformerTest.kt @@ -134,7 +134,7 @@ class ToggleBalanceTypeTransformerTest { // THEN assertThat(result.topAppBarUM).isSameInstanceAs(state.topAppBarUM) assertThat(result.marketPriceBlockState).isSameInstanceAs(state.marketPriceBlockState) - assertThat(result.stakingBlocksState).isEqualTo(state.stakingBlocksState) + assertThat(result.earnBlockState).isEqualTo(state.earnBlockState) assertThat(result.pullToRefreshConfig).isSameInstanceAs(state.pullToRefreshConfig) assertThat(result.isBalanceHidden).isEqualTo(state.isBalanceHidden) assertThat(result.isMarketPriceAvailable).isEqualTo(state.isMarketPriceAvailable) @@ -194,8 +194,9 @@ class ToggleBalanceTypeTransformerTest { tokenBalanceTypeUM = TokenBalanceTypeUM.Single, currencyIconState = CurrencyIconState.Loading, ), + notifications = persistentListOf(), marketPriceBlockState = mockk(relaxed = true), - stakingBlocksState = null, + earnBlockState = null, pullToRefreshConfig = mockk(relaxed = true), isBalanceHidden = false, isMarketPriceAvailable = false, diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateNotificationsTransformerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateNotificationsTransformerTest.kt new file mode 100644 index 0000000000..3cd6df9608 --- /dev/null +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateNotificationsTransformerTest.kt @@ -0,0 +1,540 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer + +import com.google.common.truth.Truth.assertThat +import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.core.ui.ds.message.TangemMessageEffect +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.tokens.model.warnings.CryptoCurrencyWarning +import com.tangem.domain.tokens.model.warnings.HederaWarnings +import com.tangem.domain.tokens.model.warnings.KaspaWarnings +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM.TitleState +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM +import io.mockk.mockk +import io.mockk.verify +import kotlinx.collections.immutable.persistentListOf +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +class UpdateNotificationsTransformerTest { + + private val clickIntents: TokenDetailsClickIntents = mockk(relaxed = true) + + // region Mapping warnings to notifications + + @Test + fun `GIVEN empty warnings WHEN transform THEN notifications are empty`() { + // GIVEN + val transformer = createTransformer(warnings = emptySet()) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).isEmpty() + } + + @Test + fun `GIVEN SomeNetworksUnreachable WHEN transform THEN notification with id networks_unreachable is created`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.SomeNetworksUnreachable), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("networks_unreachable") + } + + @Test + fun `GIVEN BalanceNotEnoughForFee WHEN transform THEN notification with id balance_not_enough_for_fee is created`() { + // GIVEN + val tokenCurrency: CryptoCurrency = mockk(relaxed = true) + val coinCurrency: CryptoCurrency = mockk(relaxed = true) { + io.mockk.every { network } returns mockk(relaxed = true) { + io.mockk.every { name } returns "Ethereum" + } + io.mockk.every { name } returns "Ethereum" + io.mockk.every { symbol } returns "ETH" + } + val transformer = createTransformer( + warnings = setOf( + CryptoCurrencyWarning.BalanceNotEnoughForFee( + tokenCurrency = tokenCurrency, + coinCurrency = coinCurrency, + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("balance_not_enough_for_fee") + assertThat(result.notifications.first().buttonsUM).hasSize(1) + } + + @Test + fun `GIVEN CustomTokenNotEnoughForFee with null feeCurrency WHEN transform THEN notification has no buttons`() { + // GIVEN + val currency: CryptoCurrency = mockk(relaxed = true) + val transformer = createTransformer( + warnings = setOf( + CryptoCurrencyWarning.CustomTokenNotEnoughForFee( + currency = currency, + feeCurrency = null, + networkName = "Ethereum", + feeCurrencyName = "Ethereum", + feeCurrencySymbol = "ETH", + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("custom_token_not_enough_for_fee") + assertThat(result.notifications.first().buttonsUM).isEmpty() + } + + @Test + fun `GIVEN BeaconChainShutdown WHEN transform THEN notification with id beacon_chain_shutdown is created`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.BeaconChainShutdown), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("beacon_chain_shutdown") + } + + @Test + fun `GIVEN MigrationMaticToPol WHEN transform THEN notification with id migration_matic_pol is created`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.MigrationMaticToPol), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("migration_matic_pol") + } + + @Test + fun `GIVEN MigrationClore WHEN transform THEN notification has button and id migration_clore`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.MigrationClore), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("migration_clore") + assertThat(result.notifications.first().buttonsUM).hasSize(1) + } + + @Test + fun `GIVEN HederaAssociateWarning WHEN transform THEN notification with button is created`() { + // GIVEN + val currency: CryptoCurrency = mockk(relaxed = true) + val transformer = createTransformer( + warnings = setOf(HederaWarnings.AssociateWarning(currency = currency)), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("hedera_associate") + assertThat(result.notifications.first().buttonsUM).hasSize(1) + } + + @Test + fun `GIVEN HederaAssociateWarningWithFee WHEN transform THEN notification with button is created`() { + // GIVEN + val currency: CryptoCurrency = mockk(relaxed = true) + val transformer = createTransformer( + warnings = setOf( + HederaWarnings.AssociateWarningWithFee( + currency = currency, + fee = BigDecimal("0.05"), + feeCurrencySymbol = "HBAR", + feeCurrencyDecimals = 8, + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("hedera_associate_fee") + assertThat(result.notifications.first().buttonsUM).hasSize(1) + } + + @Test + fun `GIVEN RequiredTrustline WHEN transform THEN notification with button is created`() { + // GIVEN + val currency: CryptoCurrency = mockk(relaxed = true) + val transformer = createTransformer( + warnings = setOf( + CryptoCurrencyWarning.RequiredTrustline( + currency = currency, + currencySymbol = "XLM", + requiredAmount = BigDecimal("10"), + currencyDecimals = 7, + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("required_trustline") + assertThat(result.notifications.first().buttonsUM).hasSize(1) + } + + @Test + fun `GIVEN KaspaIncompleteTransaction WHEN transform THEN notification with button and close is created`() { + // GIVEN + val currency: CryptoCurrency = mockk(relaxed = true) + val transformer = createTransformer( + warnings = setOf( + KaspaWarnings.IncompleteTransaction( + currency = currency, + amount = BigDecimal("100"), + currencySymbol = "KAS", + currencyDecimals = 8, + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(1) + assertThat(result.notifications.first().id).isEqualTo("kaspa_incomplete") + assertThat(result.notifications.first().buttonsUM).hasSize(1) + assertThat(result.notifications.first().onCloseClick).isNotNull() + } + + // endregion + + // region Skipped warnings + + @Test + fun `GIVEN ExistentialDeposit WHEN transform THEN notification is skipped`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf( + CryptoCurrencyWarning.ExistentialDeposit( + currencyName = "Polkadot", + edStringValueWithSymbol = "1 DOT", + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).isEmpty() + } + + @Test + fun `GIVEN Rent WHEN transform THEN notification is skipped`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf( + CryptoCurrencyWarning.Rent( + rent = BigDecimal("0.00001"), + exemptionAmount = BigDecimal("0.01"), + cryptoCurrency = mockk(relaxed = true), + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).isEmpty() + } + + @Test + fun `GIVEN UsedOutdatedDataWarning WHEN transform THEN notification is skipped`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.UsedOutdatedDataWarning), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).isEmpty() + } + + // endregion + + // region Message effect + + @Test + fun `GIVEN any mapped warning WHEN transform THEN messageEffect is None`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.SomeNetworksUnreachable), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications.first().messageEffect).isEqualTo(TangemMessageEffect.None) + } + + // endregion + + // region Icon + + @Test + fun `GIVEN any mapped warning WHEN transform THEN iconUM is not null`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.BeaconChainShutdown), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications.first().iconUM).isNotNull() + } + + // endregion + + // region Multiple warnings + + @Test + fun `GIVEN multiple warnings with some skipped WHEN transform THEN only mapped warnings are in notifications`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf( + CryptoCurrencyWarning.SomeNetworksUnreachable, + CryptoCurrencyWarning.BeaconChainShutdown, + CryptoCurrencyWarning.UsedOutdatedDataWarning, + CryptoCurrencyWarning.TopUpWithoutReserve, + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + + // THEN + assertThat(result.notifications).hasSize(2) + assertThat(result.notifications.map { it.id }).containsExactly( + "networks_unreachable", + "beacon_chain_shutdown", + ) + } + + // endregion + + // region Click callbacks + + @Test + fun `GIVEN HederaAssociateWarning WHEN button clicked THEN onAssociateClick is called`() { + // GIVEN + val currency: CryptoCurrency = mockk(relaxed = true) + val transformer = createTransformer( + warnings = setOf(HederaWarnings.AssociateWarning(currency = currency)), + ) + + // WHEN + val result = transformer.transform(initialState()) + result.notifications.first().buttonsUM.first().onClick() + + // THEN + verify(exactly = 1) { clickIntents.onAssociateClick() } + } + + @Test + fun `GIVEN RequiredTrustline WHEN button clicked THEN onOpenTrustlineClick is called`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf( + CryptoCurrencyWarning.RequiredTrustline( + currency = mockk(relaxed = true), + currencySymbol = "XLM", + requiredAmount = BigDecimal("10"), + currencyDecimals = 7, + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + result.notifications.first().buttonsUM.first().onClick() + + // THEN + verify(exactly = 1) { clickIntents.onOpenTrustlineClick() } + } + + @Test + fun `GIVEN KaspaIncompleteTransaction WHEN retry clicked THEN onRetryIncompleteTransactionClick is called`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf( + KaspaWarnings.IncompleteTransaction( + currency = mockk(relaxed = true), + amount = BigDecimal("100"), + currencySymbol = "KAS", + currencyDecimals = 8, + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + result.notifications.first().buttonsUM.first().onClick() + + // THEN + verify(exactly = 1) { clickIntents.onRetryIncompleteTransactionClick() } + } + + @Test + fun `GIVEN KaspaIncompleteTransaction WHEN close clicked THEN onDismissIncompleteTransactionClick is called`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf( + KaspaWarnings.IncompleteTransaction( + currency = mockk(relaxed = true), + amount = BigDecimal("100"), + currencySymbol = "KAS", + currencyDecimals = 8, + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + result.notifications.first().onCloseClick!!.invoke() + + // THEN + verify(exactly = 1) { clickIntents.onDismissIncompleteTransactionClick() } + } + + @Test + fun `GIVEN MigrationClore WHEN button clicked THEN onCloreMigrationClick is called`() { + // GIVEN + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.MigrationClore), + ) + + // WHEN + val result = transformer.transform(initialState()) + result.notifications.first().buttonsUM.first().onClick() + + // THEN + verify(exactly = 1) { clickIntents.onCloreMigrationClick() } + } + + @Test + fun `GIVEN BalanceNotEnoughForFee WHEN buy button clicked THEN onBuyCoinClick is called`() { + // GIVEN + val coinCurrency: CryptoCurrency = mockk(relaxed = true) { + io.mockk.every { network } returns mockk(relaxed = true) { + io.mockk.every { name } returns "Ethereum" + } + io.mockk.every { name } returns "Ethereum" + io.mockk.every { symbol } returns "ETH" + } + val transformer = createTransformer( + warnings = setOf( + CryptoCurrencyWarning.BalanceNotEnoughForFee( + tokenCurrency = mockk(relaxed = true), + coinCurrency = coinCurrency, + ), + ), + ) + + // WHEN + val result = transformer.transform(initialState()) + result.notifications.first().buttonsUM.first().onClick() + + // THEN + verify(exactly = 1) { clickIntents.onBuyCoinClick(coinCurrency) } + } + + // endregion + + // region State preservation + + @Test + fun `GIVEN any warnings WHEN transform THEN unrelated state fields are preserved`() { + // GIVEN + val state = initialState() + val transformer = createTransformer( + warnings = setOf(CryptoCurrencyWarning.SomeNetworksUnreachable), + ) + + // WHEN + val result = transformer.transform(state) + + // THEN + assertThat(result.topAppBarUM).isSameInstanceAs(state.topAppBarUM) + assertThat(result.balanceBlockUM).isSameInstanceAs(state.balanceBlockUM) + assertThat(result.marketPriceBlockState).isSameInstanceAs(state.marketPriceBlockState) + assertThat(result.pullToRefreshConfig).isSameInstanceAs(state.pullToRefreshConfig) + assertThat(result.isBalanceHidden).isEqualTo(state.isBalanceHidden) + assertThat(result.isMarketPriceAvailable).isEqualTo(state.isMarketPriceAvailable) + } + + // endregion + + private fun createTransformer(warnings: Set) = UpdateNotificationsTransformer( + warnings = warnings, + clickIntents = clickIntents, + ) + + private fun initialState(): TokenDetailsUM = TokenDetailsUM( + topAppBarUM = TokenDetailsTopAppBarUM( + titleState = TitleState.Simple(tokenName = "Tether"), + subtitle = stringReference("ERC-20 in Ethereum network"), + onBackClick = {}, + menuItems = persistentListOf(), + ), + balanceBlockUM = mockk(relaxed = true), + notifications = persistentListOf(), + marketPriceBlockState = mockk(relaxed = true), + earnBlockState = null, + pullToRefreshConfig = mockk(relaxed = true), + isBalanceHidden = false, + isMarketPriceAvailable = false, + ) +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateStakingNotificationTransformerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateStakingNotificationTransformerTest.kt new file mode 100644 index 0000000000..cc0a370391 --- /dev/null +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateStakingNotificationTransformerTest.kt @@ -0,0 +1,137 @@ +package com.tangem.feature.tokendetails.presentation.tokendetails.state.transformer + +import com.google.common.truth.Truth.assertThat +import com.tangem.common.ui.earn.EarnBlockUM +import com.tangem.core.ui.components.containers.pullToRefresh.PullToRefreshConfig +import com.tangem.core.ui.components.marketprice.MarketPriceBlockState +import com.tangem.core.ui.extensions.stringReference +import com.tangem.domain.appcurrency.model.AppCurrency +import com.tangem.domain.models.currency.CryptoCurrency +import com.tangem.domain.models.currency.CryptoCurrencyStatus +import com.tangem.domain.models.network.Network +import com.tangem.domain.models.staking.StakingBalance +import com.tangem.domain.staking.model.StakingAvailability +import com.tangem.domain.staking.model.StakingEntryInfo +import com.tangem.domain.staking.model.StakingOption +import com.tangem.feature.tokendetails.presentation.tokendetails.model.TokenDetailsClickIntents +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsBalanceBlockUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsTopAppBarUM.TitleState +import com.tangem.feature.tokendetails.presentation.tokendetails.state.TokenDetailsUM +import io.mockk.every +import io.mockk.mockk +import kotlinx.collections.immutable.persistentListOf +import org.junit.jupiter.api.Test +import java.math.BigDecimal + +class UpdateStakingNotificationTransformerTest { + + private val clickIntents: TokenDetailsClickIntents = mockk(relaxed = true) + + @Test + fun `GIVEN Unavailable WHEN transform THEN earnBlockState is null`() { + val transformer = createTransformer( + availability = StakingAvailability.Unavailable, + entryInfo = null, + ) + + val result = transformer.transform(initialState()) + + assertThat(result.earnBlockState).isNull() + } + + @Test + fun `GIVEN TemporaryUnavailable WHEN transform THEN TemporaryUnavailable`() { + val transformer = createTransformer( + availability = StakingAvailability.TemporaryUnavailable, + entryInfo = null, + ) + + val result = transformer.transform(initialState()) + + assertThat(result.earnBlockState).isInstanceOf(EarnBlockUM.Content::class.java) + val content = result.earnBlockState as EarnBlockUM.Content + assertThat(content.iconUM).isInstanceOf(EarnBlockUM.IconUM.Plain::class.java) + assertThat(content.trailingUM).isNull() + } + + @Test + fun `GIVEN Available without entryInfo AND no staked WHEN transform THEN null`() { + val transformer = createTransformer( + availability = availableOption(BigDecimal("4.2")), + entryInfo = null, + ) + + val result = transformer.transform(initialState()) + + assertThat(result.earnBlockState).isNull() + } + + @Test + fun `GIVEN Available with entryInfo AND no staked WHEN transform THEN Available`() { + val transformer = createTransformer( + availability = availableOption(BigDecimal("4.2")), + entryInfo = StakingEntryInfo(tokenSymbol = "SOL"), + ) + + val result = transformer.transform(initialState()) + + assertThat(result.earnBlockState).isInstanceOf(EarnBlockUM.Content::class.java) + val content = result.earnBlockState as EarnBlockUM.Content + assertThat(content.trailingUM).isInstanceOf(EarnBlockUM.TrailingUM.Button::class.java) + } + + private fun createTransformer( + availability: StakingAvailability, + entryInfo: StakingEntryInfo?, + ) = UpdateStakingNotificationTransformer( + cryptoCurrencyStatus = buildStatus(), + stakingAvailability = availability, + stakingEntryInfo = entryInfo, + appCurrency = AppCurrency.Default, + clickIntents = clickIntents, + ) + + private fun buildStatus(): CryptoCurrencyStatus { + val network = mockk(relaxed = true) { + every { rawId } returns "solana" + every { isTestnet } returns false + } + val currency = mockk(relaxed = true) { + every { symbol } returns "SOL" + every { decimals } returns 9 + every { this@mockk.network } returns network + every { id.isCoin } returns true + } + val stakingBalance = mockk(relaxed = true) + val value = mockk(relaxed = true) { + every { this@mockk.stakingBalance } returns stakingBalance + every { fiatRate } returns BigDecimal.ONE + every { yieldSupplyStatus } returns null + } + return CryptoCurrencyStatus(currency = currency, value = value) + } + + private fun availableOption(apy: BigDecimal): StakingAvailability.Available { + val option = mockk(relaxed = true) { + every { this@mockk.apy } returns apy + } + return StakingAvailability.Available(option = option) + } + + private fun initialState(): TokenDetailsUM = TokenDetailsUM( + topAppBarUM = TokenDetailsTopAppBarUM( + titleState = TitleState.Simple(tokenName = "Solana"), + subtitle = stringReference("Solana network"), + onBackClick = {}, + menuItems = persistentListOf(), + ), + balanceBlockUM = mockk(relaxed = true), + notifications = persistentListOf(), + earnBlockState = null, + marketPriceBlockState = mockk(relaxed = true), + pullToRefreshConfig = mockk(relaxed = true), + isBalanceHidden = false, + isMarketPriceAvailable = false, + ) +} \ No newline at end of file diff --git a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateTopBarMenuTransformerTest.kt b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateTopBarMenuTransformerTest.kt index f5c2d80d1e..f12747803f 100644 --- a/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateTopBarMenuTransformerTest.kt +++ b/features/tokendetails/impl/src/test/kotlin/com/tangem/feature/tokendetails/presentation/tokendetails/state/transformer/UpdateTopBarMenuTransformerTest.kt @@ -205,8 +205,9 @@ class UpdateTopBarMenuTransformerTest { menuItems = persistentListOf(), ), balanceBlockUM = mockk(relaxed = true), + notifications = persistentListOf(), marketPriceBlockState = mockk(relaxed = true), - stakingBlocksState = null, + earnBlockState = null, pullToRefreshConfig = mockk(relaxed = true), isBalanceHidden = false, isMarketPriceAvailable = false,