diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt index b0b9ac0e7c..11bdea2a34 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingUseCase.kt @@ -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.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.ungroupTokens( - tokenList: TokenList.GroupedByNetwork, - ): TokenList.Ungrouped { + private fun Raise.ungroupTokens(tokenList: TokenList.GroupedByNetwork): TokenList.Ungrouped { + validate(tokenList) + + return TokenListFactory.createUngrouped(tokenList) + } + + private fun Raise.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 + } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt index 9283f50615..2bc37f5bb1 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCase.kt @@ -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 { 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.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.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, - ) - } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt deleted file mode 100644 index eefa5d1417..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/error/mapper/TokenListSortingErrorMappers.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFactory.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFactory.kt new file mode 100644 index 0000000000..7a84e34972 --- /dev/null +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListFactory.kt @@ -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, 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, + sortType: TokensSortType, + ): TokenList.Ungrouped { + return TokenList.Ungrouped( + totalFiatBalance = totalFiatBalance, + sortedBy = sortType, + currencies = nonEmptyStatuses.sortStatuses(sortType), + ) + } + + private fun createGroupedByNetwork( + totalFiatBalance: TotalFiatBalance, + nonEmptyStatuses: NonEmptyList, + sortType: TokensSortType, + ): TokenList.GroupedByNetwork { + return TokenList.GroupedByNetwork( + totalFiatBalance = totalFiatBalance, + sortedBy = sortType, + groups = nonEmptyStatuses.groupByNetworkAndSort(sortType), + ) + } + + private fun NonEmptyList.groupByNetworkAndSort(sortType: TokensSortType): List { + return this + .groupBy { it.currency.network } + .map { (network, currencies) -> + NetworkGroup( + network = network, + currencies = currencies.sortStatuses(sortType), + ) + } + .sortGroups(sortType) + } + + private fun List.sortGroups(type: TokensSortType): List { + 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.sortStatuses(type: TokensSortType): List { + 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() + } +} \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt index 4ff3806cbc..e42588514f 100644 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt +++ b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListOperations.kt @@ -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.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.createTokenList( - currencies: NonEmptyList, - 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.createTokenList( - sortingOperations: TokenListSortingOperations, - fiatBalance: TotalFiatBalance, - isGrouped: Boolean, - ): TokenList { - return if (isGrouped) { - createGroupedTokenList(sortingOperations, fiatBalance) - } else { - createUngroupedTokenList(sortingOperations, fiatBalance) - } - } - - private fun Raise.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.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, - 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(), - ) - } - } } } \ No newline at end of file diff --git a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt b/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt deleted file mode 100644 index 7669211249..0000000000 --- a/domain/tokens/src/main/kotlin/com/tangem/domain/tokens/operations/TokenListSortingOperations.kt +++ /dev/null @@ -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, - 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> = either { - ensure(currencies.isNotEmpty()) { Error.EmptyTokens } - - if (sortByBalance) { - groupAndSortTokensByBalance() - } else { - groupTokens() - } - } - - fun getTokens(): Either> = 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.groupTokens(): NonEmptyList { - 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.groupAndSortTokensByBalance(): NonEmptyList { - val groupsWithSortedTokens = groupTokens() - .map { group -> - val tokens = group.currencies as? NonEmptyList - ?: error("Tokens can not be empty here") - group.copy(currencies = sortTokensByBalance(tokens)) - } - - return if (isAnyTokenLoading) { - groupsWithSortedTokens - } else { - sortGroupsByBalance(groupsWithSortedTokens) - } - } - - private fun sortTokensByBalance(tokens: NonEmptyList): NonEmptyList { - return if (isAnyTokenLoading) { - tokens - } else { - tokens.sortedByDescending { it.getTotalBalance() } - .toNonEmptyListOrNull() - ?: error("Tokens can not be empty here") - } - } - - private fun sortGroupsByBalance(groupsWithSortedTokens: NonEmptyList): NonEmptyList { - 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() - } -} \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingTest.kt index c34710cd97..8630063505 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ToggleTokenListGroupingTest.kt @@ -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(), - ) } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCaseTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCaseTest.kt index ebbc935479..787d55393c 100644 --- a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCaseTest.kt +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/ToggleTokenListSortingUseCaseTest.kt @@ -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(), - ) } \ No newline at end of file diff --git a/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/TokenListFactoryTest.kt b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/TokenListFactoryTest.kt new file mode 100644 index 0000000000..6e81b41b9a --- /dev/null +++ b/domain/tokens/src/test/kotlin/com/tangem/domain/tokens/operations/TokenListFactoryTest.kt @@ -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) + } +} \ No newline at end of file