Updated on 2026-08-14

This commit is contained in:
Tangem 2025-09-22 15:40:14 +04:00
parent c65a934d02
commit 8aa7bf9b75
9 changed files with 412 additions and 378 deletions

View file

@ -4,12 +4,10 @@ import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.withError
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError
import com.tangem.domain.tokens.operations.TokenListSortingOperations
import com.tangem.domain.tokens.operations.TokenListFactory
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
@ -30,36 +28,24 @@ class ToggleTokenListGroupingUseCase(
}
private fun Raise<TokenListSortingError>.groupTokens(tokenList: TokenList.Ungrouped): TokenList.GroupedByNetwork {
ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) {
TokenListSortingError.TokenListIsLoading
}
validate(tokenList)
val sortingOperations = TokenListSortingOperations(tokenList)
return TokenList.GroupedByNetwork(
groups = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
sortingOperations.getGroupedTokens().bind()
},
totalFiatBalance = tokenList.totalFiatBalance,
sortedBy = sortingOperations.getSortType(),
)
return TokenListFactory.createGroupedByNetwork(tokenList)
}
private fun Raise<TokenListSortingError>.ungroupTokens(
tokenList: TokenList.GroupedByNetwork,
): TokenList.Ungrouped {
private fun Raise<TokenListSortingError>.ungroupTokens(tokenList: TokenList.GroupedByNetwork): TokenList.Ungrouped {
validate(tokenList)
return TokenListFactory.createUngrouped(tokenList)
}
private fun Raise<TokenListSortingError>.validate(tokenList: TokenList) {
ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) {
TokenListSortingError.TokenListIsLoading
}
val sortingOperations = TokenListSortingOperations(tokenList)
return TokenList.Ungrouped(
currencies = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
sortingOperations.getTokens().bind()
},
totalFiatBalance = tokenList.totalFiatBalance,
sortedBy = sortingOperations.getSortType(),
)
ensure(tokenList.flattenCurrencies().isNotEmpty()) {
TokenListSortingError.TokenListIsEmpty
}
}
}

View file

@ -1,16 +1,14 @@
package com.tangem.domain.tokens
import arrow.core.Either
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.withError
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.error.mapper.mapToTokenListSortingError
import com.tangem.domain.tokens.operations.TokenListSortingOperations
import com.tangem.domain.tokens.operations.TokenListFactory
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
import kotlinx.coroutines.withContext
@ -21,53 +19,20 @@ class ToggleTokenListSortingUseCase(
suspend operator fun invoke(tokenList: TokenList): Either<TokenListSortingError, TokenList> {
return withContext(dispatchers.default) {
either {
when (tokenList) {
is TokenList.GroupedByNetwork -> sortGroupedTokenList(tokenList)
is TokenList.Ungrouped -> sortUngroupedTokenList(tokenList)
is TokenList.Empty -> raise(TokenListSortingError.TokenListIsEmpty)
ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) {
TokenListSortingError.TokenListIsLoading
}
TokenListFactory.create(
statuses = tokenList.flattenCurrencies(),
groupType = when (tokenList) {
is TokenList.GroupedByNetwork -> TokensGroupType.NETWORK
is TokenList.Ungrouped -> TokensGroupType.NONE
is TokenList.Empty -> raise(TokenListSortingError.TokenListIsEmpty)
},
sortType = TokensSortType.BALANCE,
)
}
}
}
private fun Raise<TokenListSortingError>.sortGroupedTokenList(
tokenList: TokenList.GroupedByNetwork,
): TokenList.GroupedByNetwork {
ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) {
TokenListSortingError.TokenListIsLoading
}
val operations = getSortingOperations(tokenList)
return tokenList.copy(
groups = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
operations.getGroupedTokens().bind()
},
sortedBy = operations.getSortType(),
)
}
private fun Raise<TokenListSortingError>.sortUngroupedTokenList(
tokenList: TokenList.Ungrouped,
): TokenList.Ungrouped {
ensure(tokenList.totalFiatBalance !is TotalFiatBalance.Loading) {
TokenListSortingError.TokenListIsLoading
}
val operations = getSortingOperations(tokenList)
return tokenList.copy(
currencies = withError(TokenListSortingOperations.Error::mapToTokenListSortingError) {
operations.getTokens().bind()
},
sortedBy = operations.getSortType(),
)
}
private fun getSortingOperations(tokenList: TokenList): TokenListSortingOperations {
return TokenListSortingOperations(
tokenList = tokenList,
sortByBalance = tokenList.sortedBy != TokensSortType.BALANCE,
)
}
}

View file

@ -1,12 +0,0 @@
package com.tangem.domain.tokens.error.mapper
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.operations.TokenListSortingOperations
internal fun TokenListSortingOperations.Error.mapToTokenListSortingError(): TokenListSortingError {
return when (this) {
is TokenListSortingOperations.Error.EmptyTokens -> TokenListSortingError.TokenListIsEmpty
is TokenListSortingOperations.Error.NetworkNotFound,
-> TokenListSortingError.UnableToSortTokenList
}
}

View file

@ -0,0 +1,132 @@
package com.tangem.domain.tokens.operations
import arrow.core.NonEmptyList
import arrow.core.toNonEmptyListOrNull
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
/**
* This factory creates a [TokenList] based on the provided list of [CryptoCurrencyStatus], [TokensGroupType],
* and [TokensSortType].
*
[REDACTED_AUTHOR]
*/
object TokenListFactory {
/**
* Creates a [TokenList] based on the provided parameters.
*
* @param statuses A list of [CryptoCurrencyStatus] representing the cryptocurrencies to be included in the token list.
* @param groupType The type of grouping to be applied to the token list, either by network or none.
* @param sortType The type of sorting to be applied to the token list, either by balance or none.
*/
fun create(statuses: List<CryptoCurrencyStatus>, groupType: TokensGroupType, sortType: TokensSortType): TokenList {
val nonEmptyStatuses = statuses.toNonEmptyListOrNull() ?: return TokenList.Empty
val totalFiatBalance = TotalFiatBalanceCalculator.calculate(statuses = nonEmptyStatuses)
return when (groupType) {
TokensGroupType.NONE -> createUngrouped(totalFiatBalance, nonEmptyStatuses, sortType)
TokensGroupType.NETWORK -> createGroupedByNetwork(totalFiatBalance, nonEmptyStatuses, sortType)
}
}
fun createUngrouped(tokenList: TokenList.GroupedByNetwork): TokenList.Ungrouped {
return createUngrouped(
totalFiatBalance = tokenList.totalFiatBalance,
nonEmptyStatuses = tokenList.flattenCurrencies().toNonEmptyListOrNull()
?: error("GroupedByNetwork.flattenCurrencies should be non empty list"),
sortType = tokenList.sortedBy,
)
}
fun createGroupedByNetwork(tokenList: TokenList.Ungrouped): TokenList.GroupedByNetwork {
return createGroupedByNetwork(
totalFiatBalance = tokenList.totalFiatBalance,
nonEmptyStatuses = tokenList.flattenCurrencies().toNonEmptyListOrNull()
?: error("Ungrouped.flattenCurrencies should be non empty list"),
sortType = tokenList.sortedBy,
)
}
private fun createUngrouped(
totalFiatBalance: TotalFiatBalance,
nonEmptyStatuses: NonEmptyList<CryptoCurrencyStatus>,
sortType: TokensSortType,
): TokenList.Ungrouped {
return TokenList.Ungrouped(
totalFiatBalance = totalFiatBalance,
sortedBy = sortType,
currencies = nonEmptyStatuses.sortStatuses(sortType),
)
}
private fun createGroupedByNetwork(
totalFiatBalance: TotalFiatBalance,
nonEmptyStatuses: NonEmptyList<CryptoCurrencyStatus>,
sortType: TokensSortType,
): TokenList.GroupedByNetwork {
return TokenList.GroupedByNetwork(
totalFiatBalance = totalFiatBalance,
sortedBy = sortType,
groups = nonEmptyStatuses.groupByNetworkAndSort(sortType),
)
}
private fun NonEmptyList<CryptoCurrencyStatus>.groupByNetworkAndSort(sortType: TokensSortType): List<NetworkGroup> {
return this
.groupBy { it.currency.network }
.map { (network, currencies) ->
NetworkGroup(
network = network,
currencies = currencies.sortStatuses(sortType),
)
}
.sortGroups(sortType)
}
private fun List<NetworkGroup>.sortGroups(type: TokensSortType): List<NetworkGroup> {
return when (type) {
TokensSortType.NONE -> this
TokensSortType.BALANCE -> {
val hasLoading = this.asSequence()
.flatMap(NetworkGroup::currencies)
.any { it.value is CryptoCurrencyStatus.Loading }
if (hasLoading) return this
sortedByDescending { group ->
group.currencies.sumOf { it.calculateBalance() }
}
}
}
}
private fun List<CryptoCurrencyStatus>.sortStatuses(type: TokensSortType): List<CryptoCurrencyStatus> {
return when (type) {
TokensSortType.NONE -> this
TokensSortType.BALANCE -> {
val hasLoading = any { it.value is CryptoCurrencyStatus.Loading }
if (hasLoading) return this
sortedByDescending { it.calculateBalance() }
}
}
}
private fun CryptoCurrencyStatus.calculateBalance(): BigDecimal {
val yieldBalance = value.yieldBalance as? YieldBalance.Data
val totalYieldBalance = yieldBalance?.getTotalWithRewardsStakingBalance(currency.network.rawId).orZero()
val totalFiatYieldBalance = totalYieldBalance.multiply(value.fiatRate.orZero())
return value.fiatAmount?.plus(totalFiatYieldBalance).orZero()
}
}

View file

@ -1,11 +1,12 @@
package com.tangem.domain.tokens.operations
import arrow.core.*
import arrow.core.raise.Raise
import arrow.core.Either
import arrow.core.left
import arrow.core.raise.either
import arrow.core.raise.withError
import arrow.core.right
import arrow.core.toNonEmptyListOrNull
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.wallet.UserWalletId
@ -30,80 +31,13 @@ internal class TokenListOperations(
}
}
private fun Raise<Error>.createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList {
private fun createTokenList(isGrouped: Boolean, isSortedByBalance: Boolean): TokenList {
val nonEmptyCurrencies = tokens.toNonEmptyListOrNull() ?: return TokenList.Empty
return createTokenList(
currencies = nonEmptyCurrencies,
fiatBalance = TotalFiatBalanceCalculator.calculate(statuses = nonEmptyCurrencies),
isGrouped = isGrouped,
isSortedByBalance = isSortedByBalance,
)
}
private fun Raise<Error>.createTokenList(
currencies: NonEmptyList<CryptoCurrencyStatus>,
fiatBalance: TotalFiatBalance,
isGrouped: Boolean,
isSortedByBalance: Boolean,
): TokenList {
val sortingOperations = TokenListSortingOperations(
currencies = currencies,
isAnyTokenLoading = currencies.any { it.value is CryptoCurrencyStatus.Loading },
sortByBalance = isSortedByBalance,
)
return createTokenList(sortingOperations, fiatBalance, isGrouped)
}
private fun Raise<Error>.createTokenList(
sortingOperations: TokenListSortingOperations,
fiatBalance: TotalFiatBalance,
isGrouped: Boolean,
): TokenList {
return if (isGrouped) {
createGroupedTokenList(sortingOperations, fiatBalance)
} else {
createUngroupedTokenList(sortingOperations, fiatBalance)
}
}
private fun Raise<Error>.createUngroupedTokenList(
sortingOperations: TokenListSortingOperations,
fiatBalance: TotalFiatBalance,
): TokenList.Ungrouped = TokenList.Ungrouped(
sortedBy = sortingOperations.getSortType(),
totalFiatBalance = fiatBalance,
currencies = withError(
transform = { e ->
Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) }
},
block = { sortingOperations.getTokens().bind() },
),
)
private fun Raise<Error>.createGroupedTokenList(
sortingOperations: TokenListSortingOperations,
fiatBalance: TotalFiatBalance,
): TokenList.GroupedByNetwork = TokenList.GroupedByNetwork(
sortedBy = sortingOperations.getSortType(),
totalFiatBalance = fiatBalance,
groups = withError(
transform = { e ->
Error.fromTokenListOperations(e) { createUnsortedUngroupedTokenList(tokens, fiatBalance) }
},
block = { sortingOperations.getGroupedTokens().bind() },
),
)
private fun createUnsortedUngroupedTokenList(
tokens: List<CryptoCurrencyStatus>,
fiatBalance: TotalFiatBalance,
): TokenList.Ungrouped {
return TokenList.Ungrouped(
sortedBy = TokensSortType.NONE,
totalFiatBalance = fiatBalance,
currencies = tokens,
return TokenListFactory.create(
statuses = nonEmptyCurrencies,
groupType = if (isGrouped) TokensGroupType.NETWORK else TokensGroupType.NONE,
sortType = if (isSortedByBalance) TokensSortType.BALANCE else TokensSortType.NONE,
)
}
@ -128,19 +62,5 @@ internal class TokenListOperations(
data class UnableToSortTokenList(val unsortedTokenList: TokenList.Ungrouped) : Error()
data class DataError(val cause: Throwable) : Error()
internal companion object {
fun fromTokenListOperations(
e: TokenListSortingOperations.Error,
createUnsortedUngroupedTokenList: () -> TokenList.Ungrouped,
): Error = when (e) {
is TokenListSortingOperations.Error.EmptyTokens,
is TokenListSortingOperations.Error.NetworkNotFound,
-> UnableToSortTokenList(
unsortedTokenList = createUnsortedUngroupedTokenList(),
)
}
}
}
}

View file

@ -1,118 +0,0 @@
package com.tangem.domain.tokens.operations
import arrow.core.Either
import arrow.core.NonEmptyList
import arrow.core.raise.Raise
import arrow.core.raise.either
import arrow.core.raise.ensure
import arrow.core.raise.ensureNotNull
import arrow.core.toNonEmptyListOrNull
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.network.Network
import com.tangem.domain.models.staking.YieldBalance
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.models.tokenlist.TokenList.GroupedByNetwork.NetworkGroup
import com.tangem.domain.staking.utils.getTotalWithRewardsStakingBalance
import com.tangem.utils.extensions.orZero
import java.math.BigDecimal
internal class TokenListSortingOperations(
private val currencies: List<CryptoCurrencyStatus>,
private val isAnyTokenLoading: Boolean,
private val sortByBalance: Boolean,
) {
constructor(
tokenList: TokenList,
sortByBalance: Boolean = tokenList.sortedBy == TokensSortType.BALANCE,
isAnyTokenLoading: Boolean = tokenList.totalFiatBalance is TotalFiatBalance.Loading,
) : this(
currencies = tokenList.flattenCurrencies(),
isAnyTokenLoading = isAnyTokenLoading,
sortByBalance = sortByBalance,
)
fun getGroupedTokens(): Either<Error, NonEmptyList<NetworkGroup>> = either {
ensure(currencies.isNotEmpty()) { Error.EmptyTokens }
if (sortByBalance) {
groupAndSortTokensByBalance()
} else {
groupTokens()
}
}
fun getTokens(): Either<Error, NonEmptyList<CryptoCurrencyStatus>> = either {
val nonEmptyCurrencies = ensureNotNull(currencies.toNonEmptyListOrNull()) {
Error.EmptyTokens
}
if (sortByBalance) sortTokensByBalance(nonEmptyCurrencies) else nonEmptyCurrencies
}
fun getSortType(): TokensSortType = if (sortByBalance) TokensSortType.BALANCE else TokensSortType.NONE
private fun Raise<Error>.groupTokens(): NonEmptyList<NetworkGroup> {
val groupedTokens = currencies
.groupBy { it.currency.network }
.map { (network, tokens) ->
NetworkGroup(
network = network,
currencies = ensureNotNull(tokens.toNonEmptyListOrNull()) { Error.EmptyTokens },
)
}
.toNonEmptyListOrNull()
return ensureNotNull(groupedTokens) { Error.EmptyTokens }
}
private fun Raise<Error>.groupAndSortTokensByBalance(): NonEmptyList<NetworkGroup> {
val groupsWithSortedTokens = groupTokens()
.map { group ->
val tokens = group.currencies as? NonEmptyList<CryptoCurrencyStatus>
?: error("Tokens can not be empty here")
group.copy(currencies = sortTokensByBalance(tokens))
}
return if (isAnyTokenLoading) {
groupsWithSortedTokens
} else {
sortGroupsByBalance(groupsWithSortedTokens)
}
}
private fun sortTokensByBalance(tokens: NonEmptyList<CryptoCurrencyStatus>): NonEmptyList<CryptoCurrencyStatus> {
return if (isAnyTokenLoading) {
tokens
} else {
tokens.sortedByDescending { it.getTotalBalance() }
.toNonEmptyListOrNull()
?: error("Tokens can not be empty here")
}
}
private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptyList<NetworkGroup>): NonEmptyList<NetworkGroup> {
return groupsWithSortedTokens
.sortedByDescending { group ->
group.currencies.sumOf { it.getTotalBalance() }
}
.toNonEmptyListOrNull()
?: error("Tokens can not be empty here")
}
private fun CryptoCurrencyStatus.getTotalBalance(): BigDecimal {
val yieldBalance = value.yieldBalance as? YieldBalance.Data
val totalYieldBalance = yieldBalance?.getTotalWithRewardsStakingBalance(currency.network.rawId).orZero()
val totalFiatYieldBalance = totalYieldBalance.multiply(value.fiatRate.orZero())
return value.fiatAmount?.plus(totalFiatYieldBalance).orZero()
}
sealed class Error {
object EmptyTokens : Error()
data class NetworkNotFound(val networkId: Network.ID) : Error()
}
}

View file

@ -2,114 +2,102 @@ package com.tangem.domain.tokens
import arrow.core.left
import arrow.core.right
import com.google.common.truth.Truth
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.mock.MockTokenLists
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class ToggleTokenListGroupingTest {
private val useCase = ToggleTokenListGroupingUseCase(
dispatchers = TestingCoroutineDispatcherProvider(),
)
@Test
fun `when list is empty then error should be received`() = runTest {
// Given
val expectedResult = TokenListSortingError.TokenListIsEmpty.left()
val useCase = getUseCase()
val expected = TokenListSortingError.TokenListIsEmpty.left()
// When
val result = useCase(MockTokenLists.emptyUngroupedTokenList)
val actual = useCase(MockTokenLists.emptyUngroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is grouped and loading then error should be received`() = runTest {
// Given
val expectedResult = TokenListSortingError.TokenListIsLoading.left()
val useCase = getUseCase()
val expected = TokenListSortingError.TokenListIsLoading.left()
// When
val result = useCase(MockTokenLists.loadingGroupedTokenList)
val actual = useCase(MockTokenLists.loadingGroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is ungrouped and loading then error should be received`() = runTest {
// Given
val expectedResult = TokenListSortingError.TokenListIsLoading.left()
val useCase = getUseCase()
val expected = TokenListSortingError.TokenListIsLoading.left()
// When
val result = useCase(MockTokenLists.loadingUngroupedTokenList)
val actual = useCase(MockTokenLists.loadingUngroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is ungrouped and sorted then sorted grouped list should be received`() = runTest {
// Given
val expectedResult = MockTokenLists.sortedGroupedTokenList.right()
val useCase = getUseCase()
val expected = MockTokenLists.sortedGroupedTokenList.right()
// When
val result = useCase(MockTokenLists.sortedUngroupedTokenList)
val actual = useCase(MockTokenLists.sortedUngroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is ungrouped and unsorted then unsorted grouped list should be received`() = runTest {
// Given
val expectedResult = MockTokenLists.unsortedGroupedTokenList.right()
val useCase = getUseCase()
val expected = MockTokenLists.unsortedGroupedTokenList.right()
// When
val result = useCase(MockTokenLists.unsortedUngroupedTokenList)
val actual = useCase(MockTokenLists.unsortedUngroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is grouped and sorted then sorted ungrouped list should be received`() = runTest {
// Given
val expectedResult = MockTokenLists.sortedUngroupedTokenList.right()
val useCase = getUseCase()
val expected = MockTokenLists.sortedUngroupedTokenList.right()
// When
val result = useCase(MockTokenLists.sortedGroupedTokenList)
val actual = useCase(MockTokenLists.sortedGroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is grouped and unsorted then unsorted ungrouped list should be received`() = runTest {
// Given
val expectedResult = MockTokenLists.unsortedUngroupedTokenList.right()
val useCase = getUseCase()
val expected = MockTokenLists.unsortedUngroupedTokenList.right()
// When
val result = useCase(MockTokenLists.unsortedGroupedTokenList)
val actual = useCase(MockTokenLists.unsortedGroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
private fun getUseCase() = ToggleTokenListGroupingUseCase(
dispatchers = TestingCoroutineDispatcherProvider(),
)
}

View file

@ -2,114 +2,102 @@ package com.tangem.domain.tokens
import arrow.core.left
import arrow.core.right
import com.google.common.truth.Truth
import com.tangem.domain.tokens.error.TokenListSortingError
import com.tangem.domain.tokens.mock.MockTokenLists
import com.tangem.utils.coroutines.TestingCoroutineDispatcherProvider
import junit.framework.TestCase.assertEquals
import kotlinx.coroutines.test.runTest
import org.junit.Test
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
internal class ToggleTokenListSortingUseCaseTest {
private val useCase = ToggleTokenListSortingUseCase(
dispatchers = TestingCoroutineDispatcherProvider(),
)
@Test
fun `when list is empty then error should be received`() = runTest {
// Given
val expectedResult = TokenListSortingError.TokenListIsEmpty.left()
val useCase = getUseCase()
val expected = TokenListSortingError.TokenListIsEmpty.left()
// When
val result = useCase(MockTokenLists.emptyTokenList)
val actual = useCase(MockTokenLists.emptyTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is grouped and loading then error should be received`() = runTest {
// Given
val expectedResult = TokenListSortingError.TokenListIsLoading.left()
val useCase = getUseCase()
val expected = TokenListSortingError.TokenListIsLoading.left()
// When
val result = useCase(MockTokenLists.loadingGroupedTokenList)
val actual = useCase(MockTokenLists.loadingGroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is ungrouped and loading then error should be received`() = runTest {
// Given
val expectedResult = TokenListSortingError.TokenListIsLoading.left()
val useCase = getUseCase()
val expected = TokenListSortingError.TokenListIsLoading.left()
// When
val result = useCase(MockTokenLists.loadingUngroupedTokenList)
val actual = useCase(MockTokenLists.loadingUngroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is grouped and unsorted then grouped and sorted list should be received`() = runTest {
// Given
val expectedResult = MockTokenLists.sortedGroupedTokenList.right()
val useCase = getUseCase()
val expected = MockTokenLists.sortedGroupedTokenList.right()
// When
val result = useCase(MockTokenLists.unsortedGroupedTokenList)
val actual = useCase(MockTokenLists.unsortedGroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is ungrouped and unsorted then ungrouped and sorted list should be received`() = runTest {
// Given
val expectedResult = MockTokenLists.sortedUngroupedTokenList.right()
val useCase = getUseCase()
val expected = MockTokenLists.sortedUngroupedTokenList.right()
// When
val result = useCase(MockTokenLists.unsortedUngroupedTokenList)
val actual = useCase(MockTokenLists.unsortedUngroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is grouped and sorted then grouped and unsorted list should be received`() = runTest {
// Given
val expectedResult = MockTokenLists.sortedGroupedTokenList.right()
val useCase = getUseCase()
val expected = MockTokenLists.sortedGroupedTokenList.right()
// When
val result = useCase(MockTokenLists.unsortedGroupedTokenList)
val actual = useCase(MockTokenLists.unsortedGroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `when list is ungrouped and sorted then ungrouped and unsorted list should be received`() = runTest {
// Given
val expectedResult = MockTokenLists.sortedUngroupedTokenList.right()
val useCase = getUseCase()
val expected = MockTokenLists.sortedUngroupedTokenList.right()
// When
val result = useCase(MockTokenLists.unsortedUngroupedTokenList)
val actual = useCase(MockTokenLists.unsortedUngroupedTokenList)
// Then
assertEquals(expectedResult, result)
Truth.assertThat(actual).isEqualTo(expected)
}
private fun getUseCase() = ToggleTokenListSortingUseCase(
dispatchers = TestingCoroutineDispatcherProvider(),
)
}

View file

@ -0,0 +1,185 @@
package com.tangem.domain.tokens.operations
import com.google.common.truth.Truth
import com.tangem.domain.models.StatusSource
import com.tangem.domain.models.TokensGroupType
import com.tangem.domain.models.TokensSortType
import com.tangem.domain.models.TotalFiatBalance
import com.tangem.domain.models.currency.CryptoCurrencyStatus
import com.tangem.domain.models.tokenlist.TokenList
import com.tangem.domain.tokens.mock.MockTokens
import com.tangem.domain.tokens.mock.MockTokensStates
import org.junit.jupiter.api.Test
import org.junit.jupiter.api.TestInstance
import java.math.BigDecimal
/**
[REDACTED_AUTHOR]
*/
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
class TokenListFactoryTest {
@Test
fun `statuses is empty, return TokenList Empty`() {
// Act
val actual = TokenListFactory.create(
statuses = emptyList(),
groupType = TokensGroupType.NONE, // doesn't matter
sortType = TokensSortType.BALANCE, // doesn't matter
)
// Assert
val expected = TokenList.Empty
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `create UNGROUPED token list WITHOUT sorting`() {
// Act
val actual = TokenListFactory.create(
statuses = MockTokensStates.loadedTokensStates,
groupType = TokensGroupType.NONE,
sortType = TokensSortType.NONE,
)
// Assert
val expected = TokenList.Ungrouped(
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal("599.50"), source = StatusSource.ACTUAL),
sortedBy = TokensSortType.NONE,
currencies = MockTokensStates.loadedTokensStates,
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `create UNGROUPED token list WITH SORTING BY BALANCE`() {
// Act
val actual = TokenListFactory.create(
statuses = MockTokensStates.loadedTokensStates,
groupType = TokensGroupType.NONE,
sortType = TokensSortType.BALANCE,
)
// Assert
val expected = TokenList.Ungrouped(
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal("599.50"), source = StatusSource.ACTUAL),
sortedBy = TokensSortType.BALANCE,
currencies = MockTokensStates.loadedTokensStates.reversed(),
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `create GROUPED token list WITHOUT sorting`() {
// Act
val actual = TokenListFactory.create(
statuses = MockTokensStates.loadedTokensStates,
groupType = TokensGroupType.NETWORK,
sortType = TokensSortType.NONE,
)
// Assert
val expected = TokenList.GroupedByNetwork(
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal("599.50"), source = StatusSource.ACTUAL),
sortedBy = TokensSortType.NONE,
groups = listOf(
TokenList.GroupedByNetwork.NetworkGroup(
network = MockTokens.token3.network,
currencies = listOf(
MockTokensStates.loadedTokensStates[0],
MockTokensStates.loadedTokensStates[1],
MockTokensStates.loadedTokensStates[2],
),
),
TokenList.GroupedByNetwork.NetworkGroup(
network = MockTokens.token6.network,
currencies = listOf(
MockTokensStates.loadedTokensStates[3],
MockTokensStates.loadedTokensStates[4],
MockTokensStates.loadedTokensStates[5],
),
),
TokenList.GroupedByNetwork.NetworkGroup(
network = MockTokens.token10.network,
currencies = listOf(
MockTokensStates.loadedTokensStates[6],
MockTokensStates.loadedTokensStates[7],
MockTokensStates.loadedTokensStates[8],
MockTokensStates.loadedTokensStates[9],
),
),
),
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `create GROUPED token list WITH SORTING BY BALANCE`() {
// Act
val actual = TokenListFactory.create(
statuses = MockTokensStates.loadedTokensStates,
groupType = TokensGroupType.NETWORK,
sortType = TokensSortType.BALANCE,
)
// Assert
val expected = TokenList.GroupedByNetwork(
totalFiatBalance = TotalFiatBalance.Loaded(amount = BigDecimal("599.50"), source = StatusSource.ACTUAL),
sortedBy = TokensSortType.BALANCE,
groups = listOf(
TokenList.GroupedByNetwork.NetworkGroup(
network = MockTokens.token10.network,
currencies = listOf(
MockTokensStates.loadedTokensStates[9],
MockTokensStates.loadedTokensStates[8],
MockTokensStates.loadedTokensStates[7],
MockTokensStates.loadedTokensStates[6],
),
),
TokenList.GroupedByNetwork.NetworkGroup(
network = MockTokens.token6.network,
currencies = listOf(
MockTokensStates.loadedTokensStates[5],
MockTokensStates.loadedTokensStates[4],
MockTokensStates.loadedTokensStates[3],
),
),
TokenList.GroupedByNetwork.NetworkGroup(
network = MockTokens.token3.network,
currencies = listOf(
MockTokensStates.loadedTokensStates[2],
MockTokensStates.loadedTokensStates[1],
MockTokensStates.loadedTokensStates[0],
),
),
),
)
Truth.assertThat(actual).isEqualTo(expected)
}
@Test
fun `sorting will not be applied if any status is Loading`() {
// Arrange
val statuses = listOf(
CryptoCurrencyStatus(currency = MockTokens.token1, value = CryptoCurrencyStatus.Loading),
MockTokensStates.tokenState10,
)
// Act
val actual = TokenListFactory.create(
statuses = statuses,
groupType = TokensGroupType.NONE,
sortType = TokensSortType.BALANCE,
)
// Assert
val expected = TokenList.Ungrouped(
totalFiatBalance = TotalFiatBalance.Loading,
sortedBy = TokensSortType.BALANCE,
currencies = statuses,
)
Truth.assertThat(actual).isEqualTo(expected)
}
}