diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 8bc51a5ecd..25cd960782 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -61,7 +61,7 @@ android { } flavorDimensions += "services" - + productFlavors { create("google") { dimension = "services" @@ -73,6 +73,15 @@ android { } } + // `src/prodDi/` holds production DI bindings for interfaces with a `mocked` counterpart. + // Wired into every build type EXCEPT `mocked`, which supplies its own bindings from `src/mocked/`. + buildTypes.configureEach { + if (name != "mocked") { + sourceSets.named(name) { + java.srcDir("src/prodDi/java") + } + } + } } configurations.all { diff --git a/app/src/main/java/com/tangem/tap/di/data/VisaStorageModule.kt b/app/src/main/java/com/tangem/tap/di/data/VisaStorageModule.kt index 1927963c23..c78909a676 100644 --- a/app/src/main/java/com/tangem/tap/di/data/VisaStorageModule.kt +++ b/app/src/main/java/com/tangem/tap/di/data/VisaStorageModule.kt @@ -1,9 +1,7 @@ package com.tangem.tap.di.data -import com.tangem.datasource.local.visa.TangemPayStorage import com.tangem.datasource.local.visa.VisaAuthTokenStorage import com.tangem.datasource.local.visa.VisaOTPStorage -import com.tangem.tap.data.DefaultTangemPayStorage import com.tangem.tap.data.DefaultVisaAuthTokenStorage import com.tangem.tap.data.DefaultVisaOTPStorage import dagger.Binds @@ -23,8 +21,4 @@ internal interface VisaStorageModule { @Binds @Singleton fun bindVisaOTPStorage(impl: DefaultVisaOTPStorage): VisaOTPStorage - - @Binds - @Singleton - fun bindTangemPayStorage(impl: DefaultTangemPayStorage): TangemPayStorage } \ No newline at end of file diff --git a/app/src/mocked/java/com/tangem/tap/core/security/MockAwareDeviceSecurityInfoProvider.kt b/app/src/mocked/java/com/tangem/tap/core/security/MockAwareDeviceSecurityInfoProvider.kt new file mode 100644 index 0000000000..9ccfeedce5 --- /dev/null +++ b/app/src/mocked/java/com/tangem/tap/core/security/MockAwareDeviceSecurityInfoProvider.kt @@ -0,0 +1,30 @@ +package com.tangem.tap.core.security + +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager +import com.tangem.security.DeviceSecurityInfoProvider + +/** In MOCK env reports a clean device; otherwise delegates (DexProtector RTC flags emulators). */ +internal class MockAwareDeviceSecurityInfoProvider( + private val real: DeviceSecurityInfoProvider, + private val apiConfigsManager: ApiConfigsManager, +) : DeviceSecurityInfoProvider { + + private val isMockMode: Boolean + get() = apiConfigsManager + .getEnvironmentConfig(ApiConfig.ID.TangemPay) + .environment == ApiEnvironment.MOCK + + override val isRooted: Boolean + get() = if (isMockMode) false else real.isRooted + + override val isBootloaderUnlocked: Boolean + get() = if (isMockMode) false else real.isBootloaderUnlocked + + override val isXposed: Boolean + get() = if (isMockMode) false else real.isXposed + + override val isVulnerableToMediaTekExploit: Boolean + get() = if (isMockMode) false else real.isVulnerableToMediaTekExploit +} \ No newline at end of file diff --git a/app/src/mocked/java/com/tangem/tap/data/MockAwareTangemPayStorage.kt b/app/src/mocked/java/com/tangem/tap/data/MockAwareTangemPayStorage.kt new file mode 100644 index 0000000000..eabe4ccda7 --- /dev/null +++ b/app/src/mocked/java/com/tangem/tap/data/MockAwareTangemPayStorage.kt @@ -0,0 +1,129 @@ +package com.tangem.tap.data + +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager +import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.TangemPayWithdrawState +import com.tangem.domain.visa.model.TangemPayAuthTokens +import javax.inject.Inject +import javax.inject.Singleton + +private const val MOCK_CUSTOMER_WALLET_ADDRESS = "0x0000000000000000000000000000000000000002" +private const val MOCK_ACCESS_TOKEN = "mock-access-token" +private const val MOCK_REFRESH_TOKEN = "mock-refresh-token" +private const val MOCK_IDEMPOTENCY_KEY = "mock-idempotency-key" +private const val MOCK_TOKEN_EXPIRES_AT = 9_999_999_999L + +/** In MOCK env returns synthetic auth tokens + customer wallet address; otherwise delegates. */ +@Singleton +internal class MockAwareTangemPayStorage @Inject constructor( + private val real: DefaultTangemPayStorage, + private val apiConfigsManager: ApiConfigsManager, +) : TangemPayStorage { + + private val isMockMode: Boolean + get() = apiConfigsManager + .getEnvironmentConfig(ApiConfig.ID.TangemPay) + .environment == ApiEnvironment.MOCK + + override suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) { + if (isMockMode) return + real.storeCustomerWalletAddress(userWalletId, customerWalletAddress) + } + + override suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String? { + if (isMockMode) return MOCK_CUSTOMER_WALLET_ADDRESS + return real.getCustomerWalletAddress(userWalletId) + } + + override suspend fun clearCustomerWalletAddress(userWalletId: UserWalletId) { + if (isMockMode) return + real.clearCustomerWalletAddress(userWalletId) + } + + override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: TangemPayAuthTokens) { + if (isMockMode) return + real.storeAuthTokens(customerWalletAddress, tokens) + } + + override suspend fun getAuthTokens(customerWalletAddress: String): TangemPayAuthTokens? { + if (isMockMode) { + return TangemPayAuthTokens( + accessToken = MOCK_ACCESS_TOKEN, + expiresAt = MOCK_TOKEN_EXPIRES_AT, + refreshToken = MOCK_REFRESH_TOKEN, + refreshExpiresAt = MOCK_TOKEN_EXPIRES_AT, + idempotencyKey = MOCK_IDEMPOTENCY_KEY, + ) + } + return real.getAuthTokens(customerWalletAddress) + } + + override suspend fun clearAuthTokens(customerWalletAddress: String) { + if (isMockMode) return + real.clearAuthTokens(customerWalletAddress) + } + + override suspend fun storeOrderId(customerWalletAddress: String, orderId: String) = + real.storeOrderId(customerWalletAddress, orderId) + + override suspend fun getOrderId(customerWalletAddress: String): String? = + real.getOrderId(customerWalletAddress) + + override suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean = + real.getAddToWalletDone(customerWalletAddress) + + override suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) = + real.storeAddToWalletDone(customerWalletAddress, isDone) + + override suspend fun clearOrderId(customerWalletAddress: String) = + real.clearOrderId(customerWalletAddress) + + override suspend fun storeCheckCustomerWalletResult(userWalletId: UserWalletId, isPaeraCustomer: Boolean) = + real.storeCheckCustomerWalletResult(userWalletId, isPaeraCustomer) + + override suspend fun checkCustomerWalletResult(userWalletId: UserWalletId): Boolean? { + if (isMockMode) return true + return real.checkCustomerWalletResult(userWalletId) + } + + override suspend fun storeActiveWithdrawOrderId(userWalletId: UserWalletId, orderId: String) = + real.storeActiveWithdrawOrderId(userWalletId, orderId) + + override suspend fun storeWithdrawOrder(userWalletId: UserWalletId, data: TangemPayWithdrawState) = + real.storeWithdrawOrder(userWalletId, data) + + override suspend fun getActiveWithdrawOrderId(userWalletId: UserWalletId): String? = + real.getActiveWithdrawOrderId(userWalletId) + + override suspend fun getWithdrawOrders(userWalletId: UserWalletId): List? = + real.getWithdrawOrders(userWalletId) + + override suspend fun deleteActiveWithdrawOrder(userWalletId: UserWalletId) = + real.deleteActiveWithdrawOrder(userWalletId) + + override suspend fun deleteWithdrawOrder(userWalletId: UserWalletId, orderId: String) = + real.deleteWithdrawOrder(userWalletId, orderId) + + override suspend fun storeHideOnboardingBanner(userWalletId: UserWalletId, hide: Boolean) = + real.storeHideOnboardingBanner(userWalletId, hide) + + override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean = + real.getHideMainOnboardingBanner(userWalletId) + + override suspend fun storeTangemPayEligibility(eligibility: Set) = + real.storeTangemPayEligibility(eligibility) + + override suspend fun getTangemPayEligibility(): Set = real.getTangemPayEligibility() + + override suspend fun storeIsTangemPayDeactivated(userWalletId: UserWalletId) = + real.storeIsTangemPayDeactivated(userWalletId) + + override suspend fun isTangemPayDeactivated(userWalletId: UserWalletId): Boolean = + real.isTangemPayDeactivated(userWalletId) + + override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) = + real.clearAll(userWalletId, customerWalletAddress) +} \ No newline at end of file diff --git a/app/src/mocked/java/com/tangem/tap/di/core/security/SecurityMockedModule.kt b/app/src/mocked/java/com/tangem/tap/di/core/security/SecurityMockedModule.kt new file mode 100644 index 0000000000..9216155ec9 --- /dev/null +++ b/app/src/mocked/java/com/tangem/tap/di/core/security/SecurityMockedModule.kt @@ -0,0 +1,25 @@ +package com.tangem.tap.di.core.security + +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager +import com.tangem.security.DeviceSecurityInfoProvider +import com.tangem.tap.core.security.DefaultDeviceSecurityInfoProvider +import com.tangem.tap.core.security.MockAwareDeviceSecurityInfoProvider +import dagger.Module +import dagger.Provides +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal object SecurityMockedModule { + + @Provides + @Singleton + fun provideDeviceSecurityInfoProvider( + apiConfigsManager: ApiConfigsManager, + ): DeviceSecurityInfoProvider { + val real = DefaultDeviceSecurityInfoProvider() + return MockAwareDeviceSecurityInfoProvider(real = real, apiConfigsManager = apiConfigsManager) + } +} \ No newline at end of file diff --git a/app/src/mocked/java/com/tangem/tap/di/data/TangemPayStorageMockedModule.kt b/app/src/mocked/java/com/tangem/tap/di/data/TangemPayStorageMockedModule.kt new file mode 100644 index 0000000000..8ccf4a8ac4 --- /dev/null +++ b/app/src/mocked/java/com/tangem/tap/di/data/TangemPayStorageMockedModule.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.di.data + +import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.tap.data.MockAwareTangemPayStorage +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TangemPayStorageMockedModule { + + @Binds + @Singleton + fun bindTangemPayStorage(impl: MockAwareTangemPayStorage): TangemPayStorage +} \ No newline at end of file diff --git a/app/src/main/java/com/tangem/tap/di/core/security/SecurityModule.kt b/app/src/prodDi/java/com/tangem/tap/di/core/security/SecurityProductionModule.kt similarity index 92% rename from app/src/main/java/com/tangem/tap/di/core/security/SecurityModule.kt rename to app/src/prodDi/java/com/tangem/tap/di/core/security/SecurityProductionModule.kt index b4e64ccb37..0f65cf0c19 100644 --- a/app/src/main/java/com/tangem/tap/di/core/security/SecurityModule.kt +++ b/app/src/prodDi/java/com/tangem/tap/di/core/security/SecurityProductionModule.kt @@ -10,7 +10,7 @@ import javax.inject.Singleton @Module @InstallIn(SingletonComponent::class) -internal object SecurityModule { +internal object SecurityProductionModule { @Provides @Singleton diff --git a/app/src/prodDi/java/com/tangem/tap/di/data/TangemPayStorageProductionModule.kt b/app/src/prodDi/java/com/tangem/tap/di/data/TangemPayStorageProductionModule.kt new file mode 100644 index 0000000000..dffc33767d --- /dev/null +++ b/app/src/prodDi/java/com/tangem/tap/di/data/TangemPayStorageProductionModule.kt @@ -0,0 +1,18 @@ +package com.tangem.tap.di.data + +import com.tangem.datasource.local.visa.TangemPayStorage +import com.tangem.tap.data.DefaultTangemPayStorage +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TangemPayStorageProductionModule { + + @Binds + @Singleton + fun bindTangemPayStorage(impl: DefaultTangemPayStorage): TangemPayStorage +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheet.kt b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheet.kt index c32033908e..9d5d8700a5 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheet.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/components/bottomsheets/message/MessageBottomSheet.kt @@ -216,7 +216,15 @@ private fun ButtonsContainer( } ?: TangemButtonIconPosition.None TangemButton( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .fillMaxWidth() + .testTag( + if (button.isPrimary) { + WarningBottomSheetTestTags.BUTTON_PRIMARY + } else { + WarningBottomSheetTestTags.BUTTON_SECONDARY + }, + ), text = button.text?.resolveReference().orEmpty(), icon = icon, onClick = { button.onClick?.invoke(closeScope) }, diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/HotWalletAccessCodeTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/HotWalletAccessCodeTestTags.kt new file mode 100644 index 0000000000..a71b596eae --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/HotWalletAccessCodeTestTags.kt @@ -0,0 +1,5 @@ +package com.tangem.core.ui.test + +object HotWalletAccessCodeTestTags { + const val ACCESS_CODE_INPUT = "HOT_WALLET_ACCESS_CODE_INPUT" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/TangemPayTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/TangemPayTestTags.kt new file mode 100644 index 0000000000..ffd6ed9254 --- /dev/null +++ b/core/ui/src/main/java/com/tangem/core/ui/test/TangemPayTestTags.kt @@ -0,0 +1,39 @@ +package com.tangem.core.ui.test + +object TangemPayTestTags { + // Main wallet screen tile (entry point into Tangem Pay) + const val MAIN_SCREEN_TILE = "TANGEM_PAY_MAIN_SCREEN_TILE" + + // Payment account details screen + const val PAYMENT_ACCOUNT_BALANCE = "TANGEM_PAY_PAYMENT_ACCOUNT_BALANCE" + const val PAYMENT_ACCOUNT_CARD_BUTTON = "TANGEM_PAY_PAYMENT_ACCOUNT_CARD_BUTTON" + + // Card details (reveal + copy) + const val CARD_DETAILS_SHOW_BUTTON = "TANGEM_PAY_CARD_DETAILS_SHOW_BUTTON" + const val CARD_DETAILS_HIDE_BUTTON = "TANGEM_PAY_CARD_DETAILS_HIDE_BUTTON" + const val CARD_DETAILS_NUMBER_VALUE = "TANGEM_PAY_CARD_DETAILS_NUMBER_VALUE" + const val CARD_DETAILS_EXPIRATION_VALUE = "TANGEM_PAY_CARD_DETAILS_EXPIRATION_VALUE" + const val CARD_DETAILS_CVC_VALUE = "TANGEM_PAY_CARD_DETAILS_CVC_VALUE" + const val CARD_DETAILS_COPY_NUMBER = "TANGEM_PAY_CARD_DETAILS_COPY_NUMBER" + const val CARD_DETAILS_COPY_EXPIRATION = "TANGEM_PAY_CARD_DETAILS_COPY_EXPIRATION" + const val CARD_DETAILS_COPY_CVC = "TANGEM_PAY_CARD_DETAILS_COPY_CVC" + + // Card management (card page settings) + const val CHANGE_PIN_ROW = "TANGEM_PAY_CHANGE_PIN_ROW" + const val FREEZE_CARD_ROW = "TANGEM_PAY_FREEZE_CARD_ROW" + + // Freeze confirmation bottom sheet + const val FREEZE_CONFIRMATION_SUBMIT_BUTTON = "TANGEM_PAY_FREEZE_CONFIRMATION_SUBMIT_BUTTON" + + // PIN entry screen + const val PIN_SCREEN_TITLE = "TANGEM_PAY_PIN_SCREEN_TITLE" + const val PIN_SCREEN_DESCRIPTION = "TANGEM_PAY_PIN_SCREEN_DESCRIPTION" + const val PIN_INPUT_FIELD = "TANGEM_PAY_PIN_INPUT_FIELD" + const val PIN_SUBMIT_BUTTON = "TANGEM_PAY_PIN_SUBMIT_BUTTON" + const val PIN_ERROR_MESSAGE = "TANGEM_PAY_PIN_ERROR_MESSAGE" + + // PIN success screen + const val PIN_SUCCESS_TITLE = "TANGEM_PAY_PIN_SUCCESS_TITLE" + const val PIN_SUCCESS_DESCRIPTION = "TANGEM_PAY_PIN_SUCCESS_DESCRIPTION" + const val PIN_DONE_BUTTON = "TANGEM_PAY_PIN_DONE_BUTTON" +} \ No newline at end of file diff --git a/core/ui/src/main/java/com/tangem/core/ui/test/WarningBottomSheetTestTags.kt b/core/ui/src/main/java/com/tangem/core/ui/test/WarningBottomSheetTestTags.kt index f4e58b8056..d8c3f37a55 100644 --- a/core/ui/src/main/java/com/tangem/core/ui/test/WarningBottomSheetTestTags.kt +++ b/core/ui/src/main/java/com/tangem/core/ui/test/WarningBottomSheetTestTags.kt @@ -4,4 +4,6 @@ object WarningBottomSheetTestTags { const val ICON = "BASE_WARNING_BOTTOM_SHEET_ICON" const val TITLE = "BASE_WARNING_BOTTOM_SHEET_TITLE" const val MESSAGE = "BASE_WARNING_BOTTOM_SHEET_MESSAGE" + const val BUTTON_PRIMARY = "BASE_WARNING_BOTTOM_SHEET_BUTTON_PRIMARY" + const val BUTTON_SECONDARY = "BASE_WARNING_BOTTOM_SHEET_BUTTON_SECONDARY" } \ No newline at end of file diff --git a/data/visa/build.gradle.kts b/data/visa/build.gradle.kts index 88d718323d..386c70565f 100644 --- a/data/visa/build.gradle.kts +++ b/data/visa/build.gradle.kts @@ -10,6 +10,16 @@ plugins { android { namespace = "com.tangem.data.visa" + + // `src/prodDi/` holds production DI bindings for TangemPay repos with a `mocked` counterpart. + // Wired into every build type EXCEPT `mocked`, which supplies its own bindings from `src/mocked/`. + buildTypes.configureEach { + if (name != "mocked") { + sourceSets.named(name) { + java.srcDir("src/prodDi/kotlin") + } + } + } } tasks.withType().configureEach { diff --git a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt index 3187eeba11..7f41dabc9c 100644 --- a/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt +++ b/data/visa/src/main/kotlin/com/tangem/data/pay/di/TangemPayDataModule.kt @@ -51,18 +51,10 @@ internal interface TangemPayDataModule { @Singleton fun bindKycRepository(repository: DefaultKycRepository): KycRepository - @Binds - @Singleton - fun bindOnboardingRepository(repository: DefaultOnboardingRepository): OnboardingRepository - @Binds @Singleton fun bindTangemPayTxHistoryRepository(repository: DefaultTangemPayTxHistoryRepository): TangemPayTxHistoryRepository - @Binds - @Singleton - fun bindCardDetailsRepository(repository: DefaultTangemPayCardDetailsRepository): TangemPayCardDetailsRepository - @Binds @Singleton fun bindTangemPaySwapRepository(repository: DefaultTangemPayWithdrawRepository): TangemPayWithdrawRepository diff --git a/data/visa/src/mocked/kotlin/com/tangem/data/pay/di/TangemPayDataMockedModule.kt b/data/visa/src/mocked/kotlin/com/tangem/data/pay/di/TangemPayDataMockedModule.kt new file mode 100644 index 0000000000..16f3f0b9db --- /dev/null +++ b/data/visa/src/mocked/kotlin/com/tangem/data/pay/di/TangemPayDataMockedModule.kt @@ -0,0 +1,24 @@ +package com.tangem.data.pay.di + +import com.tangem.data.pay.repository.MockAwareOnboardingRepository +import com.tangem.data.pay.repository.MockAwareTangemPayCardDetailsRepository +import com.tangem.domain.pay.repository.OnboardingRepository +import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TangemPayDataMockedModule { + + @Binds + @Singleton + fun bindOnboardingRepository(repository: MockAwareOnboardingRepository): OnboardingRepository + + @Binds + @Singleton + fun bindCardDetailsRepository(repository: MockAwareTangemPayCardDetailsRepository): TangemPayCardDetailsRepository +} \ No newline at end of file diff --git a/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareOnboardingRepository.kt b/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareOnboardingRepository.kt new file mode 100644 index 0000000000..9bc64e5bcf --- /dev/null +++ b/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareOnboardingRepository.kt @@ -0,0 +1,110 @@ +package com.tangem.data.pay.repository + +import arrow.core.Either +import arrow.core.right +import com.tangem.core.error.UniversalError +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager +import com.tangem.domain.models.pay.TangemPayEligibilityType +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.model.CustomerInfo +import com.tangem.domain.pay.repository.OnboardingRepository +import com.tangem.domain.visa.error.VisaApiError +import java.util.concurrent.ConcurrentHashMap +import javax.inject.Inject +import javax.inject.Singleton + +/** In MOCK env skips local-storage / signing enrollment; server calls go to WireMock. */ +@Singleton +internal class MockAwareOnboardingRepository @Inject constructor( + private val real: DefaultOnboardingRepository, + private val apiConfigsManager: ApiConfigsManager, +) : OnboardingRepository { + + private val mockOrderIds: MutableSet = ConcurrentHashMap.newKeySet() + + private val isMockMode: Boolean + get() = apiConfigsManager + .getEnvironmentConfig(ApiConfig.ID.TangemPay) + .environment == ApiEnvironment.MOCK + + override suspend fun validateDeeplink(link: String): Either { + if (isMockMode) return true.right() + return real.validateDeeplink(link) + } + + override suspend fun isTangemPayInitialDataProduced(userWalletId: UserWalletId): Boolean { + if (isMockMode) return true + return real.isTangemPayInitialDataProduced(userWalletId) + } + + override suspend fun produceInitialData(userWalletId: UserWalletId) { + if (isMockMode) return + real.produceInitialData(userWalletId) + } + + override suspend fun getCustomerInfo(userWalletId: UserWalletId): Either = + real.getCustomerInfo(userWalletId) + + override suspend fun createOrder(userWalletId: UserWalletId): Either { + if (isMockMode) { + mockOrderIds.add(userWalletId) + return MOCK_ORDER_ID.right() + } + return real.createOrder(userWalletId) + } + + override suspend fun clearOrderId(userWalletId: UserWalletId) { + if (isMockMode) { + mockOrderIds.remove(userWalletId) + return + } + real.clearOrderId(userWalletId) + } + + override suspend fun getOrderId(userWalletId: UserWalletId): String? { + if (isMockMode) return MOCK_ORDER_ID.takeIf { userWalletId in mockOrderIds } + return real.getOrderId(userWalletId) + } + + override suspend fun hasTangemPayInWallet(userWalletId: UserWalletId): Either = + real.hasTangemPayInWallet(userWalletId) + + override suspend fun checkCustomerEligibility(): List { + if (isMockMode) return listOf(TangemPayEligibilityType.DETAILS) + return real.checkCustomerEligibility() + } + + override suspend fun getCustomerEligibility(): List { + if (isMockMode) return listOf(TangemPayEligibilityType.DETAILS) + return real.getCustomerEligibility() + } + + override fun getSavedCustomerInfo(userWalletId: UserWalletId): CustomerInfo? = + real.getSavedCustomerInfo(userWalletId) + + override suspend fun getHideMainOnboardingBanner(userWalletId: UserWalletId): Boolean { + if (isMockMode) return false + return real.getHideMainOnboardingBanner(userWalletId) + } + + override suspend fun setHideMainOnboardingBanner(userWalletId: UserWalletId) { + if (isMockMode) return + real.setHideMainOnboardingBanner(userWalletId) + } + + override suspend fun disableTangemPay(userWalletId: UserWalletId): Either { + if (isMockMode) return Unit.right() + return real.disableTangemPay(userWalletId) + } + + override suspend fun isTangemPayDeactivated(userWalletId: UserWalletId): Boolean { + if (isMockMode) return false + return real.isTangemPayDeactivated(userWalletId) + } + + private companion object { + const val MOCK_ORDER_ID = "mock-order-id" + } +} \ No newline at end of file diff --git a/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareTangemPayCardDetailsRepository.kt b/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareTangemPayCardDetailsRepository.kt new file mode 100644 index 0000000000..fadd88988c --- /dev/null +++ b/data/visa/src/mocked/kotlin/com/tangem/data/pay/repository/MockAwareTangemPayCardDetailsRepository.kt @@ -0,0 +1,100 @@ +package com.tangem.data.pay.repository + +import arrow.core.Either +import arrow.core.right +import com.tangem.core.error.UniversalError +import com.tangem.datasource.api.common.config.ApiConfig +import com.tangem.datasource.api.common.config.ApiEnvironment +import com.tangem.datasource.api.common.config.managers.ApiConfigsManager +import com.tangem.domain.models.account.CardDisplayName +import com.tangem.domain.models.wallet.UserWalletId +import com.tangem.domain.pay.model.SetPinResult +import com.tangem.domain.pay.model.TangemPayCardBalance +import com.tangem.domain.pay.model.TangemPayCardDetails +import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository +import com.tangem.domain.visa.model.TangemPayCardFrozenState +import kotlinx.coroutines.flow.Flow +import javax.inject.Inject +import javax.inject.Singleton + +/** In MOCK env short-circuits RSA-encrypted flows (reveal/getPin/setPin) with hardcoded values. */ +@Singleton +internal class MockAwareTangemPayCardDetailsRepository @Inject constructor( + private val real: DefaultTangemPayCardDetailsRepository, + private val apiConfigsManager: ApiConfigsManager, +) : TangemPayCardDetailsRepository { + + private val isMockMode: Boolean + get() = apiConfigsManager + .getEnvironmentConfig(ApiConfig.ID.TangemPay) + .environment == ApiEnvironment.MOCK + + override suspend fun getCardBalance(userWalletId: UserWalletId): Either = + real.getCardBalance(userWalletId) + + override suspend fun revealCardDetails( + userWalletId: UserWalletId, + ): Either { + if (isMockMode) { + return TangemPayCardDetails( + pan = MOCK_PAN, + cvv = MOCK_CVV, + expirationYear = MOCK_EXPIRATION_YEAR, + expirationMonth = MOCK_EXPIRATION_MONTH, + ).right() + } + return real.revealCardDetails(userWalletId) + } + + override suspend fun getPin(userWalletId: UserWalletId, cardId: String): Either { + if (isMockMode) return MOCK_PIN.right() + return real.getPin(userWalletId, cardId) + } + + override suspend fun setPin(userWalletId: UserWalletId, pin: String): Either { + if (isMockMode) return SetPinResult.SUCCESS.right() + return real.setPin(userWalletId, pin) + } + + override suspend fun isAddToWalletDone(userWalletId: UserWalletId): Either = + real.isAddToWalletDone(userWalletId) + + override suspend fun setAddToWalletAsDone(userWalletId: UserWalletId): Either = + real.setAddToWalletAsDone(userWalletId) + + override suspend fun freezeCard( + userWalletId: UserWalletId, + cardId: String, + ): Either = real.freezeCard(userWalletId, cardId) + + override suspend fun unfreezeCard( + userWalletId: UserWalletId, + cardId: String, + ): Either = real.unfreezeCard(userWalletId, cardId) + + override fun cardFrozenState(cardId: String): Flow = + real.cardFrozenState(cardId) + + override suspend fun cardFrozenStateSync(cardId: String): TangemPayCardFrozenState? = + real.cardFrozenStateSync(cardId) + + override suspend fun updateCardDisplayName( + cardId: String, + userWalletId: UserWalletId, + displayName: CardDisplayName, + ): Either = real.updateCardDisplayName(cardId, userWalletId, displayName) + + override suspend fun updateCardLimit( + cardId: String, + userWalletId: UserWalletId, + limit: String, + ): Either = real.updateCardLimit(cardId, userWalletId, limit) + + private companion object { + const val MOCK_PAN = "4242 4242 4242 4242" + const val MOCK_CVV = "123" + const val MOCK_EXPIRATION_YEAR = "2028" + const val MOCK_EXPIRATION_MONTH = "12" + const val MOCK_PIN = "1234" + } +} \ No newline at end of file diff --git a/data/visa/src/prodDi/kotlin/com/tangem/data/pay/di/TangemPayDataProductionModule.kt b/data/visa/src/prodDi/kotlin/com/tangem/data/pay/di/TangemPayDataProductionModule.kt new file mode 100644 index 0000000000..02c95e98be --- /dev/null +++ b/data/visa/src/prodDi/kotlin/com/tangem/data/pay/di/TangemPayDataProductionModule.kt @@ -0,0 +1,24 @@ +package com.tangem.data.pay.di + +import com.tangem.data.pay.repository.DefaultOnboardingRepository +import com.tangem.data.pay.repository.DefaultTangemPayCardDetailsRepository +import com.tangem.domain.pay.repository.OnboardingRepository +import com.tangem.domain.pay.repository.TangemPayCardDetailsRepository +import dagger.Binds +import dagger.Module +import dagger.hilt.InstallIn +import dagger.hilt.components.SingletonComponent +import javax.inject.Singleton + +@Module +@InstallIn(SingletonComponent::class) +internal interface TangemPayDataProductionModule { + + @Binds + @Singleton + fun bindOnboardingRepository(repository: DefaultOnboardingRepository): OnboardingRepository + + @Binds + @Singleton + fun bindCardDetailsRepository(repository: DefaultTangemPayCardDetailsRepository): TangemPayCardDetailsRepository +} \ No newline at end of file diff --git a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/ui/AccessCode.kt b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/ui/AccessCode.kt index 0be023b18b..ca7747292e 100644 --- a/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/ui/AccessCode.kt +++ b/features/hot-wallet/impl/src/main/kotlin/com/tangem/features/hotwallet/accesscode/ui/AccessCode.kt @@ -11,6 +11,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -22,6 +23,7 @@ import com.tangem.core.ui.haptic.TangemHapticEffect import com.tangem.core.ui.res.LocalHapticManager import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.HotWalletAccessCodeTestTags import com.tangem.features.hotwallet.accesscode.entity.AccessCodeUM import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.delay @@ -109,6 +111,7 @@ internal fun AccessCode( pinTextColor = state.accessCodeColor, onValueChange = state.onAccessCodeChange, focusRequester = focusRequester, + modifier = Modifier.testTag(HotWalletAccessCodeTestTags.ACCESS_CODE_INPUT), ) } } diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardPageUM.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardPageUM.kt index 77b7bab3d6..c70080b726 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardPageUM.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/entity/TangemPayCardPageUM.kt @@ -36,5 +36,6 @@ internal data class TangemPayCardPageUM( @Immutable internal data class TangemPayCardPageSetting( val title: TextReference, + val testTag: String? = null, val onSettingClick: () -> Unit, ) \ No newline at end of file diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt index 59b353136e..45a98d4156 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/model/TangemPayCardPageModel.kt @@ -117,10 +117,12 @@ internal class TangemPayCardPageModel @Inject constructor( TangemPayCardPageSetting( title = TextReference.Res(R.string.tangempay_card_details_change_pin), onSettingClick = { onClickChangePIN(card.hasPinCode) }, + testTag = com.tangem.core.ui.test.TangemPayTestTags.CHANGE_PIN_ROW, ), TangemPayCardPageSetting( title = TextReference.Res(R.string.tangempay_card_details_freeze_card), onSettingClick = { onClickFreezeOrUnfreezeCard(card.isFrozen) }, + testTag = com.tangem.core.ui.test.TangemPayTestTags.FREEZE_CARD_ROW, ), TangemPayCardPageSetting( title = TextReference.Res(R.string.tangempay_card_details_reissue_card), diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlock.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlock.kt index d2feacdda6..efe8c8a8dd 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlock.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardDetailsBlock.kt @@ -29,6 +29,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.TextRange import androidx.compose.ui.text.input.ImeAction @@ -49,6 +50,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.TangemPayTestTags import com.tangem.domain.models.account.CardDisplayName import com.tangem.domain.visa.model.TangemPayCardFrozenState import com.tangem.features.tangempay.details.impl.R @@ -209,10 +211,12 @@ private fun TangemPayCardDetailsHiddenBlock(state: TangemPayCardDetailsUM, modif } TangemPayCardDetailsCustomButton( - modifier = Modifier.constrainAs(buttonRef) { - end.linkTo(parent.end) - bottom.linkTo(parent.bottom) - }, + modifier = Modifier + .constrainAs(buttonRef) { + end.linkTo(parent.end) + bottom.linkTo(parent.bottom) + } + .testTag(TangemPayTestTags.CARD_DETAILS_SHOW_BUTTON), text = stringResourceSafe(id = R.string.tangempay_card_details_show_details), onClick = state.onClick, showProgress = state.isLoading, @@ -336,6 +340,8 @@ private fun TangemPayCardDetailsShownBlock( title = stringResourceSafe(R.string.tangempay_card_details_card_number), text = cardNumber, onCopy = onCopyCardNumber, + valueTestTag = TangemPayTestTags.CARD_DETAILS_NUMBER_VALUE, + copyTestTag = TangemPayTestTags.CARD_DETAILS_COPY_NUMBER, ) Row( modifier = Modifier @@ -350,6 +356,8 @@ private fun TangemPayCardDetailsShownBlock( title = stringResourceSafe(R.string.tangempay_card_details_expiry), text = expiry, onCopy = onCopyExpiry, + valueTestTag = TangemPayTestTags.CARD_DETAILS_EXPIRATION_VALUE, + copyTestTag = TangemPayTestTags.CARD_DETAILS_COPY_EXPIRATION, ) CardDetailsTextContainer( modifier = Modifier @@ -358,13 +366,17 @@ private fun TangemPayCardDetailsShownBlock( title = stringResourceSafe(R.string.tangempay_card_details_cvc), text = cvv, onCopy = onCopyCvv, + valueTestTag = TangemPayTestTags.CARD_DETAILS_CVC_VALUE, + copyTestTag = TangemPayTestTags.CARD_DETAILS_COPY_CVC, ) } Spacer(modifier = Modifier.weight(1f)) Row { SpacerWMax() TangemPayCardDetailsCustomButton( - modifier = Modifier.padding(end = 16.dp, bottom = 8.dp), + modifier = Modifier + .padding(end = 16.dp, bottom = 8.dp) + .testTag(TangemPayTestTags.CARD_DETAILS_HIDE_BUTTON), text = stringResourceSafe(id = R.string.tangempay_card_details_hide_details), onClick = onHideDetails, showProgress = false, @@ -374,7 +386,14 @@ private fun TangemPayCardDetailsShownBlock( } @Composable -private fun CardDetailsTextContainer(title: String, text: String, onCopy: () -> Unit, modifier: Modifier = Modifier) { +private fun CardDetailsTextContainer( + title: String, + text: String, + onCopy: () -> Unit, + modifier: Modifier = Modifier, + valueTestTag: String? = null, + copyTestTag: String? = null, +) { Row( modifier = modifier .background( @@ -395,10 +414,13 @@ private fun CardDetailsTextContainer(title: String, text: String, onCopy: () -> text = text, style = TangemTheme.typography.body2, color = TangemTheme.colors.text.constantWhite, + modifier = if (valueTestTag != null) Modifier.testTag(valueTestTag) else Modifier, ) } IconButton( - modifier = Modifier.size(TangemTheme.dimens.size32), + modifier = Modifier + .size(TangemTheme.dimens.size32) + .then(if (copyTestTag != null) Modifier.testTag(copyTestTag) else Modifier), onClick = onCopy, ) { Icon( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardPageScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardPageScreen.kt index a9a05b551e..4b8dbc7446 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardPageScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayCardPageScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.runtime.remember import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.testTag import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.util.fastForEach import com.tangem.core.ui.components.appbar.AppBarWithBackButton @@ -169,7 +170,8 @@ private fun TangemPayCardPageSettingRow( modifier = modifier .fillMaxWidth() .clickable(onClick = onClick) - .padding(TangemTheme.dimens.spacing12), + .padding(TangemTheme.dimens.spacing12) + .then(if (item.testTag != null) Modifier.testTag(item.testTag) else Modifier), contentAlignment = Alignment.CenterStart, ) { Text( diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinCodeSuccessScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinCodeSuccessScreen.kt index d1c902a313..eda90333b9 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinCodeSuccessScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinCodeSuccessScreen.kt @@ -8,6 +8,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.vector.ImageVector +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.vectorResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -23,6 +24,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.TangemPayTestTags import com.tangem.features.tangempay.details.impl.R @Composable @@ -43,7 +45,7 @@ internal fun TangemPayChangePinCodeSuccessScreen(onClick: () -> Unit, modifier: titleAlignment = Alignment.CenterHorizontally, ) Column( - modifier + modifier = Modifier .fillMaxSize(), horizontalAlignment = Alignment.CenterHorizontally, ) { @@ -58,7 +60,8 @@ internal fun TangemPayChangePinCodeSuccessScreen(onClick: () -> Unit, modifier: .fillMaxWidth() .padding(horizontal = 16.dp) .padding(bottom = 16.dp) - .navigationBarsPadding(), + .navigationBarsPadding() + .testTag(TangemPayTestTags.PIN_DONE_BUTTON), text = stringResourceSafe(R.string.common_done), onClick = onClick, ) @@ -101,14 +104,18 @@ private fun SuccessContent(modifier: Modifier = Modifier) { ) SpacerH32() Text( - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier + .padding(horizontal = 32.dp) + .testTag(TangemPayTestTags.PIN_SUCCESS_TITLE), text = stringResourceSafe(R.string.tangempay_card_details_change_pin_success_title), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, ) SpacerH12() Text( - modifier = Modifier.padding(horizontal = 32.dp), + modifier = Modifier + .padding(horizontal = 32.dp) + .testTag(TangemPayTestTags.PIN_SUCCESS_DESCRIPTION), text = stringResourceSafe(R.string.tangempay_card_details_change_pin_success_description), style = TangemTheme.typography.body1, color = TangemTheme.colors.text.secondary, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinScreen.kt index c1eaca6abc..88a3a9ca2e 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayChangePinScreen.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.graphics.Color.Companion.Transparent import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalSoftwareKeyboardController +import androidx.compose.ui.platform.testTag import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextAlign @@ -31,6 +32,7 @@ import com.tangem.core.ui.extensions.resolveReference import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme +import com.tangem.core.ui.test.TangemPayTestTags import com.tangem.features.tangempay.details.impl.R import com.tangem.features.tangempay.entity.TangemPayChangePinUM import kotlinx.coroutines.delay @@ -52,7 +54,7 @@ internal fun TangemPayChangePinScreen( ) Column( - modifier = modifier + modifier = Modifier .fillMaxWidth() .padding(top = 48.dp) .padding(horizontal = 36.dp) @@ -64,6 +66,7 @@ internal fun TangemPayChangePinScreen( style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, textAlign = TextAlign.Center, + modifier = Modifier.testTag(TangemPayTestTags.PIN_SCREEN_TITLE), ) SpacerH16() @@ -73,6 +76,7 @@ internal fun TangemPayChangePinScreen( style = TangemTheme.typography.body1, color = TangemTheme.colors.text.secondary, textAlign = TextAlign.Center, + modifier = Modifier.testTag(TangemPayTestTags.PIN_SCREEN_DESCRIPTION), ) SpacerH(26.dp) @@ -84,7 +88,8 @@ internal fun TangemPayChangePinScreen( modifier = Modifier .imePadding() .padding(start = 16.dp, end = 16.dp, bottom = 16.dp) - .fillMaxWidth(), + .fillMaxWidth() + .testTag(TangemPayTestTags.PIN_SUBMIT_BUTTON), primaryButton = NavigationButton( textReference = resourceReference(R.string.common_submit), onClick = state.onSubmitClick, @@ -117,6 +122,7 @@ private fun PinCodeSection(state: TangemPayChangePinUM, modifier: Modifier = Mod style = TangemTheme.typography.caption2, color = TangemTheme.colors.text.warning, textAlign = TextAlign.Center, + modifier = Modifier.testTag(TangemPayTestTags.PIN_ERROR_MESSAGE), ) } } @@ -150,7 +156,8 @@ private fun PinCode( .clickable { focusRequester.requestFocus() keyboardController?.show() - }, + } + .testTag(TangemPayTestTags.PIN_INPUT_FIELD), textStyle = TangemTheme.typography.h1.copy(color = Transparent), keyboardOptions = KeyboardOptions( keyboardType = KeyboardType.NumberPassword, diff --git a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt index 938b054d98..2349905b14 100644 --- a/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt +++ b/features/tangempay/details/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayDetailsScreen.kt @@ -49,6 +49,7 @@ import com.tangem.core.ui.extensions.resourceReference import com.tangem.core.ui.extensions.stringResourceSafe import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.TangemPayTestTags import com.tangem.core.ui.test.TokenDetailsTopBarTestTags import com.tangem.core.ui.test.WalletConnectBottomSheetTestTags import com.tangem.features.tangempay.components.express.PreviewEmptyExpressTransactionsComponent @@ -304,7 +305,7 @@ private fun FiatBalance( ), ) is TangemPayDetailsBalanceBlockState.Content -> Text( - modifier = modifier, + modifier = modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE), text = state.fiatBalance.orMaskWithStars(isBalanceHidden), style = TangemTheme.typography.h2.applyBladeBrush( isEnabled = state.isBalanceFlickering, @@ -312,7 +313,7 @@ private fun FiatBalance( ), ) is TangemPayDetailsBalanceBlockState.Error -> Text( - modifier = modifier, + modifier = modifier.testTag(TangemPayTestTags.PAYMENT_ACCOUNT_BALANCE), text = DASH_SIGN.orMaskWithStars(isBalanceHidden), style = TangemTheme.typography.h2, color = TangemTheme.colors.text.primary1, diff --git a/features/tangempay/main/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayMainBlockContent.kt b/features/tangempay/main/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayMainBlockContent.kt index 54da2fa3ec..deecec789f 100644 --- a/features/tangempay/main/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayMainBlockContent.kt +++ b/features/tangempay/main/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayMainBlockContent.kt @@ -16,6 +16,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip import androidx.compose.ui.layout.layoutId +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign @@ -31,6 +32,7 @@ import com.tangem.core.ui.ds.row.TangemRowLayoutId import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreviewRedesign +import com.tangem.core.ui.test.TangemPayTestTags import com.tangem.features.tangempay.entity.TangemPayMainUM import com.tangem.features.tangempay.main.impl.R import com.tangem.utils.StringsSigns.DASH_SIGN @@ -90,7 +92,8 @@ private fun TangemPayMainContent( modifier = modifier .clip(RoundedCornerShape(size = 18.dp)) .background(TangemTheme.colors2.surface.level3) - .clickableSingle(onClick = payMainUM.onClick), + .clickableSingle(onClick = payMainUM.onClick) + .testTag(TangemPayTestTags.MAIN_SCREEN_TILE), ) { Image( painter = painterResource(R.drawable.img_visa_36), diff --git a/features/tangempay/main/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayMainBlockContentLegacy.kt b/features/tangempay/main/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayMainBlockContentLegacy.kt index e95dcc262b..f72aecb6f4 100644 --- a/features/tangempay/main/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayMainBlockContentLegacy.kt +++ b/features/tangempay/main/impl/src/main/kotlin/com/tangem/features/tangempay/ui/TangemPayMainBlockContentLegacy.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.draw.clip +import androidx.compose.ui.platform.testTag import androidx.compose.ui.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.tooling.preview.Preview @@ -29,6 +30,7 @@ import com.tangem.core.ui.components.text.applyBladeBrush import com.tangem.core.ui.extensions.* import com.tangem.core.ui.res.TangemTheme import com.tangem.core.ui.res.TangemThemePreview +import com.tangem.core.ui.test.TangemPayTestTags import com.tangem.features.tangempay.entity.TangemPayMainUM import com.tangem.features.tangempay.main.impl.R import com.tangem.utils.StringsSigns.DASH_SIGN @@ -57,7 +59,7 @@ private fun TangemPayMainBlockContent( modifier: Modifier = Modifier, ) { Surface( - modifier = modifier, + modifier = modifier.testTag(TangemPayTestTags.MAIN_SCREEN_TILE), shape = TangemTheme.shapes.roundedCornersXMedium, color = TangemTheme.colors.background.primary, onClick = state.onClick,