Updated on 2026-08-14
This commit is contained in:
commit
31a8ee5dd4
18 changed files with 579 additions and 366 deletions
|
|
@ -20,7 +20,7 @@ import com.tangem.domain.addressbook.model.Contact
|
|||
import com.tangem.domain.addressbook.model.ContactName
|
||||
import com.tangem.domain.addressbook.usecase.DeleteContactUseCase
|
||||
import com.tangem.domain.addressbook.usecase.GetContactByIdUseCase
|
||||
import com.tangem.domain.addressbook.usecase.ValidateContactNameUseCase
|
||||
import com.tangem.domain.addressbook.validation.ContactNameValidator
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
|
|
@ -55,7 +55,7 @@ internal class EditContactModel @Inject constructor(
|
|||
private val resultHolder: AddressBookResultHolder,
|
||||
private val messageSender: UiMessageSender,
|
||||
private val userWalletsListRepository: UserWalletsListRepository,
|
||||
private val validateContactNameUseCase: ValidateContactNameUseCase,
|
||||
private val contactNameValidator: ContactNameValidator,
|
||||
private val saveContactInteractor: SaveContactInteractor,
|
||||
private val getContactByIdUseCase: GetContactByIdUseCase,
|
||||
private val deleteContactUseCase: DeleteContactUseCase,
|
||||
|
|
@ -232,6 +232,7 @@ internal class EditContactModel @Inject constructor(
|
|||
selectedWallet.mapNotNull { it?.walletId }.distinctUntilChanged(),
|
||||
) { name, walletId -> name to walletId }
|
||||
.mapLatest { (name, walletId) -> validateName(name, walletId) }
|
||||
.flowOn(dispatchers.default)
|
||||
.onEach { error -> stateController.update(UpdateNameErrorTransformer(error)) }
|
||||
.launchIn(modelScope)
|
||||
}
|
||||
|
|
@ -417,7 +418,7 @@ internal class EditContactModel @Inject constructor(
|
|||
private suspend fun validateName(name: String, walletId: UserWalletId): TextReference? {
|
||||
if (name.isBlank()) return null
|
||||
if (name == loadedContact.value?.name?.value) return null
|
||||
val error = validateContactNameUseCase(walletId, name).leftOrNull() ?: return null
|
||||
val error = contactNameValidator.validate(walletId, name).leftOrNull() ?: return null
|
||||
if (error is ContactNameValidationError.Format && error.error is ContactName.Error.Empty) return null
|
||||
return ContactNameErrorConverter().convert(error)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -58,12 +58,16 @@ internal class AddressBookListModel @Inject constructor(
|
|||
private val selectedWalletId = MutableStateFlow<String?>(value = null)
|
||||
|
||||
private val allContacts: SharedFlow<List<VerifiedContact>> =
|
||||
getVerifiedContactsInteractor(query = "", userWalletId = null)
|
||||
getVerifiedContactsInteractor.getVerifiedContacts(query = "", userWalletId = null)
|
||||
.shareIn(modelScope, SharingStarted.Lazily, replay = 1)
|
||||
|
||||
init {
|
||||
val matchedContacts = searchQuery.flatMapLatest { query ->
|
||||
if (query.isBlank()) allContacts else getVerifiedContactsInteractor(query = query, userWalletId = null)
|
||||
if (query.isBlank()) {
|
||||
allContacts
|
||||
} else {
|
||||
getVerifiedContactsInteractor.getVerifiedContacts(query = query, userWalletId = null)
|
||||
}
|
||||
}
|
||||
combine(
|
||||
allContacts,
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ import com.tangem.domain.addressbook.interactor.SaveContactInteractor
|
|||
import com.tangem.domain.addressbook.model.*
|
||||
import com.tangem.domain.addressbook.usecase.DeleteContactUseCase
|
||||
import com.tangem.domain.addressbook.usecase.GetContactByIdUseCase
|
||||
import com.tangem.domain.addressbook.usecase.ValidateContactNameUseCase
|
||||
import com.tangem.domain.addressbook.validation.ContactNameValidator
|
||||
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||
import com.tangem.domain.models.account.AccountStatus
|
||||
import com.tangem.domain.models.account.CryptoPortfolioIcon
|
||||
|
|
@ -56,7 +56,7 @@ internal class EditContactModelTest {
|
|||
private val resultHolder = AddressBookResultHolder()
|
||||
private val messageSender: UiMessageSender = mockk(relaxed = true)
|
||||
private val userWalletsListRepository: UserWalletsListRepository = mockk(relaxed = true)
|
||||
private val validateContactNameUseCase: ValidateContactNameUseCase = mockk()
|
||||
private val contactNameValidator: ContactNameValidator = mockk()
|
||||
private val saveContactInteractor: SaveContactInteractor = mockk()
|
||||
private val getContactByIdUseCase: GetContactByIdUseCase = mockk()
|
||||
private val deleteContactUseCase: DeleteContactUseCase = mockk()
|
||||
|
|
@ -75,7 +75,7 @@ internal class EditContactModelTest {
|
|||
fun setUp() {
|
||||
// Default: no wallets loaded, name always valid. Individual tests override as needed.
|
||||
setupWallets(wallets = emptyList(), selected = null)
|
||||
coEvery { validateContactNameUseCase(any(), any()) } returns ContactName("Satoshi").getOrNull()!!.right()
|
||||
coEvery { contactNameValidator.validate(any(), any()) } returns ContactName("Satoshi").getOrNull()!!.right()
|
||||
every { portfolioFetcherFactory.create(any(), any()) } returns portfolioFetcher
|
||||
every { portfolioSelectorController.selectedAccountWithData(any()) } returns selectedWalletData
|
||||
// No existing contact by default; a StateFlow<null> never surfaces a contact and never completes.
|
||||
|
|
@ -396,7 +396,7 @@ internal class EditContactModelTest {
|
|||
val walletA = createWallet(id = "aa", name = "Wallet A")
|
||||
setupWallets(wallets = listOf(walletA), selected = walletA)
|
||||
coEvery {
|
||||
validateContactNameUseCase(any(), any())
|
||||
contactNameValidator.validate(any(), any())
|
||||
} returns ContactNameValidationError.Duplicate.left()
|
||||
val model = createModel(testScope = this)
|
||||
advanceUntilIdle()
|
||||
|
|
@ -434,7 +434,7 @@ internal class EditContactModelTest {
|
|||
val walletB = createWallet(id = "bb", name = "Wallet B")
|
||||
setupWallets(wallets = listOf(walletA, walletB), selected = walletA)
|
||||
coEvery {
|
||||
validateContactNameUseCase(walletB.walletId, "Satoshi")
|
||||
contactNameValidator.validate(walletB.walletId, "Satoshi")
|
||||
} returns ContactNameValidationError.Duplicate.left()
|
||||
val model = createModel(testScope = this)
|
||||
advanceUntilIdle()
|
||||
|
|
@ -489,7 +489,7 @@ internal class EditContactModelTest {
|
|||
// Arrange
|
||||
val walletA = createWallet(id = "aa", name = "Wallet A")
|
||||
setupWallets(wallets = listOf(walletA), selected = walletA)
|
||||
coEvery { validateContactNameUseCase(any(), any()) } returns ContactNameValidationError.Duplicate.left()
|
||||
coEvery { contactNameValidator.validate(any(), any()) } returns ContactNameValidationError.Duplicate.left()
|
||||
val model = createModel(testScope = this)
|
||||
advanceUntilIdle()
|
||||
|
||||
|
|
@ -927,7 +927,7 @@ internal class EditContactModelTest {
|
|||
resultHolder = resultHolder,
|
||||
messageSender = messageSender,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
validateContactNameUseCase = validateContactNameUseCase,
|
||||
contactNameValidator = contactNameValidator,
|
||||
saveContactInteractor = saveContactInteractor,
|
||||
getContactByIdUseCase = getContactByIdUseCase,
|
||||
deleteContactUseCase = deleteContactUseCase,
|
||||
|
|
|
|||
|
|
@ -61,7 +61,7 @@ internal class AddressBookListModelTest {
|
|||
@Test
|
||||
fun `GIVEN feature just opened WHEN contacts not yet loaded THEN Loading state`() = runTest {
|
||||
// Arrange — the interactor has not emitted yet (books still syncing).
|
||||
every { getVerifiedContactsInteractor(query = "", userWalletId = null) } returns emptyFlow()
|
||||
every { getVerifiedContactsInteractor.getVerifiedContacts(query = "", userWalletId = null) } returns emptyFlow()
|
||||
|
||||
// Act
|
||||
val model = createModel(testScope = this, mode = AddressBookRoute.ListMode.Default)
|
||||
|
|
@ -73,7 +73,7 @@ internal class AddressBookListModelTest {
|
|||
@Test
|
||||
fun `GIVEN default mode AND verified contacts WHEN created THEN content shown`() = runTest {
|
||||
// Arrange
|
||||
every { getVerifiedContactsInteractor(query = "", userWalletId = null) } returns
|
||||
every { getVerifiedContactsInteractor.getVerifiedContacts(query = "", userWalletId = null) } returns
|
||||
flowOf(listOf(verifiedContact(id = "1", name = "Alice"), verifiedContact(id = "2", name = "Bob")))
|
||||
|
||||
// Act
|
||||
|
|
@ -89,7 +89,7 @@ internal class AddressBookListModelTest {
|
|||
@Test
|
||||
fun `GIVEN default mode AND no contacts WHEN created THEN empty state`() = runTest {
|
||||
// Arrange
|
||||
every { getVerifiedContactsInteractor(query = "", userWalletId = null) } returns flowOf(emptyList())
|
||||
every { getVerifiedContactsInteractor.getVerifiedContacts(query = "", userWalletId = null) } returns flowOf(emptyList())
|
||||
|
||||
// Act
|
||||
val model = createModel(testScope = this, mode = AddressBookRoute.ListMode.Default)
|
||||
|
|
@ -103,7 +103,7 @@ internal class AddressBookListModelTest {
|
|||
fun `GIVEN default mode WHEN contact clicked THEN editor opened with contact id`() = runTest {
|
||||
// Arrange
|
||||
var clickedId: String? = null
|
||||
every { getVerifiedContactsInteractor(query = "", userWalletId = null) } returns
|
||||
every { getVerifiedContactsInteractor.getVerifiedContacts(query = "", userWalletId = null) } returns
|
||||
flowOf(listOf(verifiedContact(id = "42", name = "Alice")))
|
||||
val model = createModel(
|
||||
testScope = this,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue