Updated on 2026-08-14
This commit is contained in:
parent
71003c43db
commit
3cd5c708c9
7 changed files with 564 additions and 145 deletions
|
|
@ -0,0 +1,127 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account.CryptoPortfolio.Error.AccountNameError
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Represents an account
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
sealed interface Account {
|
||||
|
||||
/** Unique identifier of the account */
|
||||
val accountId: AccountId
|
||||
|
||||
/** Name of the account */
|
||||
val name: AccountName
|
||||
|
||||
/** The identifier of the user wallet associated with the account */
|
||||
val userWalletId: UserWalletId
|
||||
get() = accountId.userWalletId
|
||||
|
||||
/**
|
||||
* Represents a crypto portfolio account
|
||||
*
|
||||
* @property accountId unique identifier of the account
|
||||
* @property name name of the account
|
||||
* @property icon icon representing the account
|
||||
* @property derivationIndex index used for derivation of the account
|
||||
* @property isArchived indicates whether the account is archived
|
||||
* @property cryptoCurrencyList list of tokens associated with the account
|
||||
*/
|
||||
data class CryptoPortfolio private constructor(
|
||||
override val accountId: AccountId,
|
||||
override val name: AccountName,
|
||||
val icon: CryptoPortfolioIcon,
|
||||
val derivationIndex: Int,
|
||||
val isArchived: Boolean,
|
||||
val cryptoCurrencyList: CryptoCurrencyList,
|
||||
) : Account {
|
||||
|
||||
/** Indicates if the account is the main account */
|
||||
val isMainAccount: Boolean
|
||||
get() = derivationIndex == 0
|
||||
|
||||
/** Number of tokens in the account */
|
||||
val tokensCount: Int
|
||||
get() = cryptoCurrencyList.currencies.size
|
||||
|
||||
/** Number of distinct networks in the account */
|
||||
val networksCount: Int
|
||||
get() = cryptoCurrencyList.currencies.map(CryptoCurrency::network).distinct().size
|
||||
|
||||
/**
|
||||
* Represents a list of tokens in the account
|
||||
*
|
||||
* @property currencies set of cryptocurrencies in the account
|
||||
* @property sortType sorting type for the tokens
|
||||
* @property groupType grouping type for the tokens
|
||||
*/
|
||||
data class CryptoCurrencyList(
|
||||
val currencies: Set<CryptoCurrency>,
|
||||
val sortType: TokensSortType,
|
||||
val groupType: TokensGroupType,
|
||||
)
|
||||
|
||||
/**
|
||||
* Represents possible errors when creating a crypto portfolio account
|
||||
*/
|
||||
sealed interface Error {
|
||||
|
||||
/** Error indicating that the account name is blank */
|
||||
data class AccountNameError(val cause: AccountName.Error) : Error {
|
||||
override fun toString(): String = cause.toString()
|
||||
}
|
||||
|
||||
/** Error indicating that the derivation index is negative */
|
||||
data object NegativeDerivationIndex : Error {
|
||||
override fun toString(): String = "${this::class.simpleName}: Derivation index must be non-negative"
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
/**
|
||||
* Constructor for creating a [CryptoPortfolio] instance
|
||||
*
|
||||
* @param accountId unique identifier of the account
|
||||
* @param name name of the account
|
||||
* @param accountIcon icon representing the account
|
||||
* @param derivationIndex index used for derivation of the account
|
||||
* @param isArchived indicates whether the account is archived
|
||||
* @param cryptoCurrencyList list of tokens associated with the account
|
||||
*/
|
||||
@Suppress("LongParameterList")
|
||||
operator fun invoke(
|
||||
accountId: AccountId,
|
||||
name: String,
|
||||
accountIcon: CryptoPortfolioIcon,
|
||||
derivationIndex: Int,
|
||||
isArchived: Boolean,
|
||||
cryptoCurrencyList: CryptoCurrencyList,
|
||||
): Either<Error, CryptoPortfolio> {
|
||||
return either {
|
||||
val accountName = AccountName(name).mapLeft(::AccountNameError).bind()
|
||||
|
||||
ensure(derivationIndex >= 0) { Error.NegativeDerivationIndex }
|
||||
|
||||
CryptoPortfolio(
|
||||
accountId = accountId,
|
||||
name = accountName,
|
||||
icon = accountIcon,
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = isArchived,
|
||||
cryptoCurrencyList = cryptoCurrencyList,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
|
||||
/**
|
||||
* Represents a unique identifier for an account
|
||||
*
|
||||
* @property value a unique string value that distinguishes this account
|
||||
* @property userWalletId the identifier of the user wallet associated with the account
|
||||
*/
|
||||
data class AccountId(
|
||||
val value: String,
|
||||
val userWalletId: UserWalletId,
|
||||
)
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.raise.ensure
|
||||
|
||||
/**
|
||||
* Represents an account name
|
||||
*
|
||||
* @property value the validated account name as a string
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
data class AccountName private constructor(
|
||||
val value: String,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Represents possible validation errors
|
||||
*/
|
||||
sealed interface Error {
|
||||
|
||||
/**
|
||||
* Error indicating that the account name is blank
|
||||
*/
|
||||
data object Empty : Error {
|
||||
override fun toString(): String = "${Empty::class.simpleName}: Account name cannot be blank"
|
||||
}
|
||||
|
||||
/**
|
||||
* Error indicating that the account name exceeds the maximum allowed length
|
||||
*/
|
||||
data object ExceedsMaxLength : Error {
|
||||
override fun toString(): String {
|
||||
return "${ExceedsMaxLength::class.simpleName}: Account name cannot exceed $MAX_LENGTH characters"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
||||
private const val MAX_LENGTH = 20
|
||||
|
||||
/**
|
||||
* Factory method to create an `AccountName` instance.
|
||||
* Validates the input string to ensure it is not blank and does not exceed the maximum length.
|
||||
*
|
||||
* @param value the input string representing the account name
|
||||
*/
|
||||
operator fun invoke(value: String): Either<Error, AccountName> = either {
|
||||
val trimmedValue = value.trim()
|
||||
|
||||
ensure(trimmedValue.isNotBlank()) { Error.Empty }
|
||||
ensure(trimmedValue.length <= MAX_LENGTH) { Error.ExceedsMaxLength }
|
||||
|
||||
AccountName(value = trimmedValue)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,48 +1,26 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Companion.ofCustomAccount
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Companion.ofDefaultCustomAccount
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Companion.ofMainAccount
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import kotlinx.serialization.Serializable
|
||||
|
||||
/**
|
||||
* Represents an icon for an [Account.CryptoPortfolio] account
|
||||
*
|
||||
* @property type the type of the account icon
|
||||
* @property value the type of the account icon
|
||||
* @property color the color of the account icon
|
||||
*
|
||||
* @constructor [ofMainAccount], [ofCustomAccount]
|
||||
* @constructor [ofMainAccount], [ofDefaultCustomAccount]
|
||||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@Serializable
|
||||
data class CryptoPortfolioIcon private constructor(
|
||||
val type: Type,
|
||||
val value: Icon,
|
||||
val color: Color,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Represents the type of an account icon. Can either be a specific [Icon] or a [Symbol]
|
||||
*/
|
||||
@Serializable
|
||||
sealed interface Type {
|
||||
|
||||
/**
|
||||
* Represents a specific predefined icon type
|
||||
*
|
||||
* @property value the predefined [Icon] of the icon
|
||||
*/
|
||||
@Serializable
|
||||
data class Icon(val value: CryptoPortfolioIcon.Icon) : Type
|
||||
|
||||
/**
|
||||
* Represents an icon with a letter
|
||||
*
|
||||
* @property value the letter used as the icon
|
||||
*/
|
||||
@Serializable
|
||||
data class Symbol(val value: Char) : Type
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum class representing the icons of accounts
|
||||
*/
|
||||
|
|
@ -91,59 +69,44 @@ data class CryptoPortfolioIcon private constructor(
|
|||
|
||||
companion object {
|
||||
|
||||
private val defaultMainAccountType: Icon = Icon.Star
|
||||
private val defaultMainAccountColor: Color = Color.Azure
|
||||
private val defaultMainAccountIcon: Icon = Icon.Star
|
||||
private val excludedCustomAccountIcons: Set<Icon> = setOf(Icon.Letter, Icon.Star)
|
||||
private const val HASH_MULTIPLIER = 31
|
||||
|
||||
/**
|
||||
* Creates an [CryptoPortfolioIcon] for the Main account, ensuring the color is not in the excluded set.
|
||||
* Creating a [CryptoPortfolioIcon] for the Main account with default values.
|
||||
* The color is derived from the [UserWalletId].
|
||||
*
|
||||
* @param exclude excluded colors that are already used for main accounts
|
||||
* @param userWalletId the ID of the user wallet
|
||||
*/
|
||||
fun ofMainAccount(exclude: Set<Color>): CryptoPortfolioIcon {
|
||||
val isDefaultColorBusy = defaultMainAccountColor in exclude
|
||||
fun ofMainAccount(userWalletId: UserWalletId): CryptoPortfolioIcon {
|
||||
val colors = Color.entries
|
||||
val hash = userWalletId.value.fold(0) { acc, byte -> acc * HASH_MULTIPLIER + byte }
|
||||
|
||||
val color = if (isDefaultColorBusy) {
|
||||
val colorsWithExcluded = Color.entries - exclude
|
||||
val index = (hash and Int.MAX_VALUE) % colors.size
|
||||
val color = colors[index]
|
||||
|
||||
val availableColors = if (colorsWithExcluded.isNotEmpty()) {
|
||||
colorsWithExcluded
|
||||
} else {
|
||||
Color.entries
|
||||
}
|
||||
|
||||
availableColors.random()
|
||||
} else {
|
||||
defaultMainAccountColor
|
||||
}
|
||||
|
||||
return CryptoPortfolioIcon(
|
||||
type = Type.Icon(value = defaultMainAccountType),
|
||||
color = color,
|
||||
)
|
||||
return CryptoPortfolioIcon(value = defaultMainAccountIcon, color = color)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an [CryptoPortfolioIcon] for a user account based on the account name
|
||||
*
|
||||
* @param accountName the name of the account, used to determine the letter for the icon
|
||||
*/
|
||||
fun ofCustomAccount(accountName: String): CryptoPortfolioIcon {
|
||||
fun ofDefaultCustomAccount(): CryptoPortfolioIcon {
|
||||
val icon = (Icon.entries - excludedCustomAccountIcons).random()
|
||||
val color = Color.entries.random()
|
||||
|
||||
return CryptoPortfolioIcon(
|
||||
type = Type.Symbol(value = accountName.first()),
|
||||
color = color,
|
||||
)
|
||||
return CryptoPortfolioIcon(value = icon, color = color)
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a [CryptoPortfolioIcon] for a user account with a specific type and color
|
||||
*
|
||||
* @param type the type of the account icon
|
||||
* @param value the icon of the account
|
||||
* @param color the color of the account icon
|
||||
*/
|
||||
fun ofCustomAccount(type: Type, color: Color): CryptoPortfolioIcon {
|
||||
return CryptoPortfolioIcon(type = type, color = color)
|
||||
fun ofCustomAccount(value: Icon, color: Color): CryptoPortfolioIcon {
|
||||
return CryptoPortfolioIcon(value = value, color = color)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,67 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import com.google.common.truth.Truth
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class AccountNameTest {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun invoke(model: InvokeTestModel) {
|
||||
// Act
|
||||
val actual = AccountName(value = model.value)
|
||||
|
||||
// Assert
|
||||
actual
|
||||
.onRight {
|
||||
val expected = model.expected.getOrNull()!!
|
||||
Truth.assertThat(it).isEqualTo(expected)
|
||||
}
|
||||
.onLeft {
|
||||
val expected = model.expected.leftOrNull()!!
|
||||
Truth.assertThat(it).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
InvokeTestModel(
|
||||
value = "",
|
||||
expected = AccountName.Error.Empty.left(),
|
||||
),
|
||||
InvokeTestModel(
|
||||
value = " ",
|
||||
expected = AccountName.Error.Empty.left(),
|
||||
),
|
||||
InvokeTestModel(
|
||||
value = "a".repeat(21),
|
||||
expected = AccountName.Error.ExceedsMaxLength.left(),
|
||||
),
|
||||
"a".repeat(20).let { value ->
|
||||
InvokeTestModel(
|
||||
value = value,
|
||||
expected = AccountName(value = value),
|
||||
)
|
||||
},
|
||||
InvokeTestModel(
|
||||
value = " name ",
|
||||
expected = AccountName(value = "name"),
|
||||
),
|
||||
InvokeTestModel(
|
||||
value = "Main Account",
|
||||
expected = AccountName(value = "Main Account"),
|
||||
),
|
||||
)
|
||||
|
||||
data class InvokeTestModel(
|
||||
val value: String,
|
||||
val expected: Either<AccountName.Error, AccountName>,
|
||||
)
|
||||
}
|
||||
|
|
@ -0,0 +1,187 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.models.TokensGroupType
|
||||
import com.tangem.domain.models.TokensSortType
|
||||
import com.tangem.domain.models.account.Account.CryptoPortfolio.CryptoCurrencyList
|
||||
import com.tangem.domain.models.account.Account.CryptoPortfolio.Error.AccountNameError
|
||||
import com.tangem.domain.models.currency.CryptoCurrency
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.every
|
||||
import io.mockk.mockk
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
|
||||
/**
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
class AccountTest {
|
||||
|
||||
@Test
|
||||
fun `Account userWalletId`() {
|
||||
// Arrange
|
||||
val userWalletId = UserWalletId("011")
|
||||
|
||||
// Act
|
||||
val actual = createCryptoPortfolioStub(userWalletId = userWalletId).userWalletId
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual).isEqualTo(userWalletId)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CryptoPortfolio isMainAccount`() {
|
||||
// Arrange
|
||||
val derivationIndex0 = 0
|
||||
val derivationIndex1 = 1
|
||||
|
||||
// Act
|
||||
val actual1 = createCryptoPortfolioStub(derivationIndex = derivationIndex0)
|
||||
.isMainAccount
|
||||
|
||||
val actual2 = createCryptoPortfolioStub(derivationIndex = derivationIndex1)
|
||||
.isMainAccount
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual1).isTrue()
|
||||
Truth.assertThat(actual2).isFalse()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CryptoPortfolio tokensCount`() {
|
||||
// Arrange
|
||||
val emptyCurrencies = emptySet<CryptoCurrency>()
|
||||
val filledCurrencies = setOf(mockk<CryptoCurrency>())
|
||||
|
||||
// Act
|
||||
val actual1 = createCryptoPortfolioStub(currencies = emptyCurrencies)
|
||||
.tokensCount
|
||||
|
||||
val actual2 = createCryptoPortfolioStub(currencies = filledCurrencies)
|
||||
.tokensCount
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual1).isEqualTo(0)
|
||||
Truth.assertThat(actual2).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `CryptoPortfolio networksCount`() {
|
||||
// Arrange
|
||||
val emptyCurrencies = emptySet<CryptoCurrency>()
|
||||
val filledCurrencies = setOf(
|
||||
mockk<CryptoCurrency> {
|
||||
every { network } returns mockk()
|
||||
},
|
||||
)
|
||||
|
||||
// Act
|
||||
val actual1 = createCryptoPortfolioStub(currencies = emptyCurrencies)
|
||||
.networksCount
|
||||
|
||||
val actual2 = createCryptoPortfolioStub(currencies = filledCurrencies)
|
||||
.networksCount
|
||||
|
||||
// Assert
|
||||
Truth.assertThat(actual1).isEqualTo(0)
|
||||
Truth.assertThat(actual2).isEqualTo(1)
|
||||
}
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class CreateCryptoPortfolio {
|
||||
|
||||
@Test
|
||||
fun `invoke returns AccountNameError`() {
|
||||
// Arrange
|
||||
val name = ""
|
||||
|
||||
// Act
|
||||
val actual = Account.CryptoPortfolio(
|
||||
accountId = mockk(),
|
||||
name = name,
|
||||
accountIcon = mockk(),
|
||||
derivationIndex = 0,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = mockk(),
|
||||
)
|
||||
.leftOrNull()!!
|
||||
|
||||
// Assert
|
||||
val expected = AccountNameError(cause = AccountName.Error.Empty)
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns NegativeDerivationIndex`() {
|
||||
// Arrange
|
||||
val derivationIndex = -1
|
||||
|
||||
// Act
|
||||
val actual = Account.CryptoPortfolio(
|
||||
accountId = mockk(),
|
||||
name = "Test Account",
|
||||
accountIcon = mockk(),
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = mockk(),
|
||||
)
|
||||
.leftOrNull()!!
|
||||
|
||||
// Assert
|
||||
val expected = Account.CryptoPortfolio.Error.NegativeDerivationIndex
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `invoke returns CryptoPortfolio`() {
|
||||
// Act
|
||||
val actual = Account.CryptoPortfolio(
|
||||
accountId = AccountId(
|
||||
value = "value",
|
||||
userWalletId = UserWalletId("011"),
|
||||
),
|
||||
name = "Test Account",
|
||||
accountIcon = CryptoPortfolioIcon.ofMainAccount(userWalletId = UserWalletId("011")),
|
||||
derivationIndex = 0,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = CryptoCurrencyList(
|
||||
currencies = emptySet(),
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
)
|
||||
.getOrNull()!!
|
||||
|
||||
// Assert
|
||||
val expected = createCryptoPortfolioStub()
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
}
|
||||
}
|
||||
|
||||
private fun createCryptoPortfolioStub(
|
||||
userWalletId: UserWalletId = UserWalletId("011"),
|
||||
name: String = "Test Account",
|
||||
derivationIndex: Int = 0,
|
||||
currencies: Set<CryptoCurrency> = emptySet(),
|
||||
): Account.CryptoPortfolio {
|
||||
return Account.CryptoPortfolio.invoke(
|
||||
accountId = AccountId(
|
||||
value = "value",
|
||||
userWalletId = userWalletId,
|
||||
),
|
||||
name = name,
|
||||
accountIcon = CryptoPortfolioIcon.ofMainAccount(userWalletId),
|
||||
derivationIndex = derivationIndex,
|
||||
isArchived = false,
|
||||
cryptoCurrencyList = CryptoCurrencyList(
|
||||
currencies = currencies,
|
||||
sortType = TokensSortType.NONE,
|
||||
groupType = TokensGroupType.NONE,
|
||||
),
|
||||
)
|
||||
.getOrNull()!!
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,14 @@
|
|||
package com.tangem.domain.models.account
|
||||
|
||||
import com.google.common.truth.Truth
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.*
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Color
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon.Icon
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import io.mockk.every
|
||||
import io.mockk.mockkObject
|
||||
import io.mockk.unmockkObject
|
||||
import io.mockk.verify
|
||||
import io.mockk.verifyOrder
|
||||
import org.junit.jupiter.api.Nested
|
||||
import org.junit.jupiter.api.Test
|
||||
import org.junit.jupiter.api.TestInstance
|
||||
import org.junit.jupiter.params.ParameterizedTest
|
||||
import org.junit.jupiter.params.provider.MethodSource
|
||||
|
|
@ -23,159 +24,160 @@ class CryptoPortfolioIconTest {
|
|||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class OfMainAccount {
|
||||
|
||||
@Test
|
||||
fun `ofMainAccount with empty exclude`() {
|
||||
// Act
|
||||
val actual = CryptoPortfolioIcon.ofMainAccount(exclude = emptySet())
|
||||
|
||||
// Assert
|
||||
val expectedColor = Color.Azure
|
||||
Truth.assertThat(actual.color).isEqualTo(expectedColor)
|
||||
|
||||
val expectedType = Type.Icon(value = Icon.Star)
|
||||
Truth.assertThat(actual.type).isEqualTo(expectedType)
|
||||
}
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun ofMainAccount(model: OfMainAccountModel) {
|
||||
// Arrange
|
||||
mockkObject(Random.Default)
|
||||
|
||||
val size = (Color.entries.size - model.exclude.size).takeIf { it > 0 } ?: Color.entries.size
|
||||
every { Random.nextInt(size) } returns model.randomNextInt
|
||||
|
||||
// Act
|
||||
val actual = CryptoPortfolioIcon.ofMainAccount(exclude = model.exclude)
|
||||
val actual = CryptoPortfolioIcon.ofMainAccount(userWalletId = model.userWalletId)
|
||||
|
||||
// Assert
|
||||
val expectedColor = model.expectedColor
|
||||
Truth.assertThat(actual.color).isEqualTo(expectedColor)
|
||||
|
||||
val expectedType = Type.Icon(value = Icon.Star)
|
||||
Truth.assertThat(actual.type).isEqualTo(expectedType)
|
||||
|
||||
verify(exactly = 1) { Random.nextInt(size) }
|
||||
|
||||
unmockkObject(Random.Default)
|
||||
val expectedIcon = Icon.Star
|
||||
Truth.assertThat(actual.value).isEqualTo(expectedIcon)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
// If the default color is already occupied (present in the exclude set), a random color from the
|
||||
// remaining available colors will be selected for the main account icon.
|
||||
OfMainAccountModel(
|
||||
exclude = setOf(Color.Azure),
|
||||
randomNextInt = 0,
|
||||
expectedColor = Color.entries[1],
|
||||
userWalletId = UserWalletId("1234567890abcdef"),
|
||||
expectedColor = Color.Pattypan,
|
||||
),
|
||||
OfMainAccountModel(
|
||||
exclude = setOf(Color.Azure, Color.CaribbeanBlue),
|
||||
randomNextInt = 0,
|
||||
expectedColor = Color.entries[2],
|
||||
userWalletId = UserWalletId("27163F47405CE73110837F24DF82607FF11C7AF9D78C93F409E4FEAFF3400C8F"),
|
||||
expectedColor = Color.CandyGrapeFizz,
|
||||
),
|
||||
// If all colors are already occupied, a random one will be selected.
|
||||
OfMainAccountModel(
|
||||
exclude = Color.entries.toSet(),
|
||||
randomNextInt = 1,
|
||||
expectedColor = Color.entries[1],
|
||||
userWalletId = UserWalletId("64A3791C180584C700EBECD6EAB36CBC34643BB449BC87761104C09F41DBCF3D"),
|
||||
expectedColor = Color.PalatinateBlue,
|
||||
),
|
||||
OfMainAccountModel(
|
||||
userWalletId = UserWalletId("01C061A99FCCEDA87933267EBAB3513592F83AD2E27BDA6EE5546BA96009D21F"),
|
||||
expectedColor = Color.Pelati,
|
||||
),
|
||||
OfMainAccountModel(
|
||||
userWalletId = UserWalletId("6D387A8FA5D2AF95F601EBCA8736D73D2ED53159835D8C407FBD4BBB10290C8B"),
|
||||
expectedColor = Color.CaribbeanBlue,
|
||||
),
|
||||
OfMainAccountModel(
|
||||
userWalletId = UserWalletId("33FCD9B9982C31648C235AE55A29212D567ECD3BA24BE4227D1A01897ADBC959"),
|
||||
expectedColor = Color.SweetDesire,
|
||||
),
|
||||
OfMainAccountModel(
|
||||
userWalletId = UserWalletId("197C8C5AA59270F3E9E1F30799A007D193DA596E6DC24C37D002C2EC203C2A0B"),
|
||||
expectedColor = Color.VitalGreen,
|
||||
),
|
||||
OfMainAccountModel(
|
||||
userWalletId = UserWalletId("ACF90C18393828958B5E795771F0692A00D3D7ADC092F726AB4A7E3116DD6E6E"),
|
||||
expectedColor = Color.Pattypan,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class OfMainAccountModel(
|
||||
val exclude: Set<Color>,
|
||||
val randomNextInt: Int,
|
||||
val userWalletId: UserWalletId,
|
||||
val expectedColor: Color,
|
||||
)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class OfCustomAccountBasedOnName {
|
||||
inner class OfDefaultCustomAccount {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun ofCustomAccount(model: OfCustomAccountModel.BasedOnName) {
|
||||
fun ofCustomAccount(model: OfDefaultCustomAccountModel) {
|
||||
// Arrange
|
||||
val availableIcons = Icon.entries - setOf(Icon.Letter, Icon.Star)
|
||||
|
||||
mockkObject(Random.Default)
|
||||
|
||||
every { Random.nextInt(until = Color.entries.size) } returns model.randomNextInt
|
||||
every { Random.nextInt(until = availableIcons.size) } returns model.randomIconIndex
|
||||
every { Random.nextInt(until = Color.entries.size) } returns model.randomColorIndex
|
||||
|
||||
// Act
|
||||
val actual = CryptoPortfolioIcon.ofCustomAccount(accountName = model.accountName)
|
||||
val actual = CryptoPortfolioIcon.ofDefaultCustomAccount()
|
||||
|
||||
// Assert
|
||||
val expectedColor = model.expectedColor
|
||||
Truth.assertThat(actual.color).isEqualTo(expectedColor)
|
||||
val expected = model.expected
|
||||
Truth.assertThat(actual).isEqualTo(expected)
|
||||
|
||||
val expectedType = Type.Symbol(value = model.accountName.first())
|
||||
Truth.assertThat(actual.type).isEqualTo(expectedType)
|
||||
|
||||
verify(exactly = 1) { Random.nextInt(until = Color.entries.size) }
|
||||
verifyOrder {
|
||||
Random.nextInt(until = availableIcons.size)
|
||||
Random.nextInt(until = Color.entries.size)
|
||||
}
|
||||
|
||||
unmockkObject(Random.Default)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
OfCustomAccountModel.BasedOnName(
|
||||
accountName = "New account",
|
||||
randomNextInt = 0,
|
||||
expectedColor = Color.entries[0],
|
||||
OfDefaultCustomAccountModel(
|
||||
randomIconIndex = 0,
|
||||
randomColorIndex = 0,
|
||||
expected = CryptoPortfolioIcon.ofCustomAccount(value = Icon.User, color = Color.Azure),
|
||||
),
|
||||
OfCustomAccountModel.BasedOnName(
|
||||
accountName = "Awesome",
|
||||
randomNextInt = Color.entries.lastIndex,
|
||||
expectedColor = Color.entries.last(),
|
||||
OfDefaultCustomAccountModel(
|
||||
randomIconIndex = 1,
|
||||
randomColorIndex = 1,
|
||||
expected = CryptoPortfolioIcon.ofCustomAccount(value = Icon.Family, color = Color.CaribbeanBlue),
|
||||
),
|
||||
OfDefaultCustomAccountModel(
|
||||
randomIconIndex = Icon.entries.lastIndex - 2,
|
||||
randomColorIndex = Color.entries.lastIndex,
|
||||
expected = CryptoPortfolioIcon.ofCustomAccount(value = Icon.Gift, color = Color.VitalGreen),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
data class OfDefaultCustomAccountModel(
|
||||
val randomIconIndex: Int,
|
||||
val randomColorIndex: Int,
|
||||
val expected: CryptoPortfolioIcon,
|
||||
)
|
||||
|
||||
@Nested
|
||||
@TestInstance(TestInstance.Lifecycle.PER_CLASS)
|
||||
inner class OfCustomAccountWithTypeAndColor {
|
||||
|
||||
@ParameterizedTest
|
||||
@MethodSource("provideTestModels")
|
||||
fun ofCustomAccount(model: OfCustomAccountModel.WithTypeAndColor) {
|
||||
fun ofCustomAccount(model: OfCustomAccountModel) {
|
||||
// Act
|
||||
val actual = CryptoPortfolioIcon.ofCustomAccount(type = model.type, color = model.color)
|
||||
val actual = CryptoPortfolioIcon.ofCustomAccount(value = model.icon, color = model.color)
|
||||
|
||||
// Assert
|
||||
val expectedColor = model.expectedColor
|
||||
Truth.assertThat(actual.color).isEqualTo(expectedColor)
|
||||
|
||||
val expectedType = model.expectedType
|
||||
Truth.assertThat(actual.type).isEqualTo(expectedType)
|
||||
Truth.assertThat(actual.value).isEqualTo(expectedType)
|
||||
}
|
||||
|
||||
private fun provideTestModels() = listOf(
|
||||
OfCustomAccountModel.WithTypeAndColor(
|
||||
type = Type.Icon(value = Icon.User),
|
||||
OfCustomAccountModel(
|
||||
icon = Icon.User,
|
||||
color = Color.CaribbeanBlue,
|
||||
expectedType = Type.Icon(value = Icon.User),
|
||||
expectedType = Icon.User,
|
||||
expectedColor = Color.CaribbeanBlue,
|
||||
),
|
||||
OfCustomAccountModel.WithTypeAndColor(
|
||||
type = Type.Symbol(value = 'A'),
|
||||
OfCustomAccountModel(
|
||||
icon = Icon.Letter,
|
||||
color = Color.DullLavender,
|
||||
expectedType = Type.Symbol(value = 'A'),
|
||||
expectedType = Icon.Letter,
|
||||
expectedColor = Color.DullLavender,
|
||||
),
|
||||
OfCustomAccountModel(
|
||||
icon = Icon.Star,
|
||||
color = Color.DullLavender,
|
||||
expectedType = Icon.Star,
|
||||
expectedColor = Color.DullLavender,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
sealed interface OfCustomAccountModel {
|
||||
|
||||
data class BasedOnName(
|
||||
val accountName: String,
|
||||
val randomNextInt: Int,
|
||||
val expectedColor: Color,
|
||||
) : OfCustomAccountModel
|
||||
|
||||
data class WithTypeAndColor(
|
||||
val type: Type,
|
||||
val color: Color,
|
||||
val expectedType: Type,
|
||||
val expectedColor: Color,
|
||||
) : OfCustomAccountModel
|
||||
}
|
||||
data class OfCustomAccountModel(
|
||||
val icon: Icon,
|
||||
val color: Color,
|
||||
val expectedType: Icon,
|
||||
val expectedColor: Color,
|
||||
)
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue