Updated on 2026-08-14
This commit is contained in:
commit
6437018fc8
1160 changed files with 35466 additions and 6540 deletions
|
|
@ -113,12 +113,14 @@ dependencies {
|
||||||
implementation(projects.domain.legacy)
|
implementation(projects.domain.legacy)
|
||||||
implementation(projects.libs.blockchainSdk)
|
implementation(projects.libs.blockchainSdk)
|
||||||
implementation(projects.domain.account)
|
implementation(projects.domain.account)
|
||||||
|
implementation(projects.domain.account.status)
|
||||||
implementation(projects.domain.models)
|
implementation(projects.domain.models)
|
||||||
implementation(projects.domain.core)
|
implementation(projects.domain.core)
|
||||||
api(projects.domain.common)
|
api(projects.domain.common)
|
||||||
implementation(projects.domain.card)
|
implementation(projects.domain.card)
|
||||||
implementation(projects.domain.demo)
|
implementation(projects.domain.demo)
|
||||||
implementation(projects.domain.demo.models)
|
implementation(projects.domain.demo.models)
|
||||||
|
implementation(projects.domain.express)
|
||||||
implementation(projects.domain.wallets)
|
implementation(projects.domain.wallets)
|
||||||
implementation(projects.domain.wallets.models)
|
implementation(projects.domain.wallets.models)
|
||||||
implementation(projects.domain.settings)
|
implementation(projects.domain.settings)
|
||||||
|
|
@ -160,6 +162,7 @@ dependencies {
|
||||||
implementation(projects.domain.walletManager.models)
|
implementation(projects.domain.walletManager.models)
|
||||||
implementation(projects.domain.yieldSupply)
|
implementation(projects.domain.yieldSupply)
|
||||||
implementation(projects.domain.blockaid)
|
implementation(projects.domain.blockaid)
|
||||||
|
implementation(projects.domain.hotWallet)
|
||||||
|
|
||||||
implementation(projects.common)
|
implementation(projects.common)
|
||||||
implementation(projects.common.routing)
|
implementation(projects.common.routing)
|
||||||
|
|
@ -167,6 +170,7 @@ dependencies {
|
||||||
implementation(projects.core.analytics)
|
implementation(projects.core.analytics)
|
||||||
implementation(projects.core.analytics.models)
|
implementation(projects.core.analytics.models)
|
||||||
implementation(projects.core.navigation)
|
implementation(projects.core.navigation)
|
||||||
|
implementation(projects.core.abTests)
|
||||||
implementation(projects.core.configToggles)
|
implementation(projects.core.configToggles)
|
||||||
implementation(projects.core.res)
|
implementation(projects.core.res)
|
||||||
implementation(projects.core.ui)
|
implementation(projects.core.ui)
|
||||||
|
|
@ -209,6 +213,7 @@ dependencies {
|
||||||
implementation(projects.data.swap)
|
implementation(projects.data.swap)
|
||||||
implementation(projects.data.walletManager)
|
implementation(projects.data.walletManager)
|
||||||
implementation(projects.data.yieldSupply)
|
implementation(projects.data.yieldSupply)
|
||||||
|
implementation(projects.data.hotWallet)
|
||||||
|
|
||||||
/** Features */
|
/** Features */
|
||||||
implementation(projects.features.referral.impl)
|
implementation(projects.features.referral.impl)
|
||||||
|
|
@ -245,6 +250,8 @@ dependencies {
|
||||||
implementation(projects.features.walletSettings.impl)
|
implementation(projects.features.walletSettings.impl)
|
||||||
implementation(projects.features.markets.api)
|
implementation(projects.features.markets.api)
|
||||||
implementation(projects.features.markets.impl)
|
implementation(projects.features.markets.impl)
|
||||||
|
implementation(projects.features.feed.api)
|
||||||
|
implementation(projects.features.feed.impl)
|
||||||
implementation(projects.features.onramp.api)
|
implementation(projects.features.onramp.api)
|
||||||
implementation(projects.features.onramp.impl)
|
implementation(projects.features.onramp.impl)
|
||||||
implementation(projects.features.onboardingV2.api)
|
implementation(projects.features.onboardingV2.api)
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,6 @@ abstract class BaseTestCase : TestCase(
|
||||||
private val apiEnvironmentRule = ApiEnvironmentRule()
|
private val apiEnvironmentRule = ApiEnvironmentRule()
|
||||||
private val permissionRule = GrantPermissionRule.grant(
|
private val permissionRule = GrantPermissionRule.grant(
|
||||||
Manifest.permission.POST_NOTIFICATIONS,
|
Manifest.permission.POST_NOTIFICATIONS,
|
||||||
Manifest.permission.CAMERA,
|
|
||||||
)
|
)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,32 @@ object TestConstants {
|
||||||
const val TOTAL_BALANCE = "$3,299.18"
|
const val TOTAL_BALANCE = "$3,299.18"
|
||||||
|
|
||||||
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
|
const val RECIPIENT_ADDRESS = "0x7d1afa7b718fb893db30a3abc0cfc608aacfebb0"
|
||||||
|
const val ETHEREUM_ADDRESS = "0x9FFd974772bDA94D288240C1B22F367Ce75CcD7f"
|
||||||
|
const val ETHEREUM_RECIPIENT_ADDRESS = "0x5aa711F440Eb6d4361148bBD89d03464628ace84"
|
||||||
|
const val ETHEREUM_RECIPIENT_SHORTENED_ADDRESS = "0x5aa711F440Eb6d43...89d03464628ace84"
|
||||||
const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3"
|
const val BITCOIN_ADDRESS = "bc1qtg9aa6jcpqtvun0pe0uct7sxm8nq2nsxfmfxm3"
|
||||||
const val CARDANO_ADDRESS = "addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
|
const val CARDANO_ADDRESS =
|
||||||
|
"addr1q8f9499e58k4hhfd9vhawprxt3xd94x7rmlyp33ee4xkatakcl2zgkrg0p6ceqkndtkw4cumfe9enhdph8yhuswn785srksm9p"
|
||||||
|
const val SOLANA_RECIPIENT_ADDRESS = "5fcy9woa8Di1QHcce65CsV3XKrxdB2pD4HJx5xx82ipM"
|
||||||
|
const val POLKADOT_RECIPIENT_ADDRESS = "143TfgFYAFfM86LRzt4UcFNU3KosxCndBCVz2U5HCxpLidKZ"
|
||||||
|
const val XRP_X_ADDRESS = "XVVzUQZFrRo3MUW3HT1WUaAfE5iNr7USr9ER3DvsKmguJRa"
|
||||||
|
const val XRP_RECIPIENT_ADDRESS = "rNeY28BPda6jp5N5oESZzd2ZN7eMZy8jNf"
|
||||||
|
const val XRP_X_RECIPIENT_ADDRESS = "XVNpatGaPNpPbiAJsQz6ZczZFt97QBbFimbtGZnxBMC7ug9"
|
||||||
|
const val XRP_X_RECIPIENT_ADDRESS_WITH_TAG = "XVNpatGaPNpPbiAJsQz6ZczZFt97QB6jeQWyCM3uCaDHVix"
|
||||||
|
const val ENS_NAME = "louded.eth"
|
||||||
|
const val ENS_ETHEREUM_RECIPIENT_ADDRESS = "0x0211ff638298adcbdc99c177dc2e95ec69948865"
|
||||||
|
const val ENS_ETHEREUM_RECIPIENT_SHORTENED_ADDRESS = "0x0211ff63829..."
|
||||||
|
const val KUSAMA_RECIPIENT_ADDRESS = "CqNrR92Hh76vW69vDBL5iATrZoYkk9nj67iVSUbb2YHtktn"
|
||||||
|
const val AZERO_RECIPIENT_ADDRESS = "5EA4p6DZdbt2vLZySML2dG3ZsnNrenEWZHnVCScQh4iq2KZo"
|
||||||
|
const val TEZOS_RECIPIENT_ADDRESS = "tz1eBdC2JkU2bxgZssweLo6D3wCkWN12ioHW"
|
||||||
|
const val KASPA_RECIPIENT_ADDRESS = "kaspa:qypc4aywf95rken57kclwdjvycugnacstrh5uzmu0a7pjtxas366ktgj5jzu8t6"
|
||||||
|
const val CHIA_RECIPIENT_ADDRESS = "xch1dz2vua2qvxpx6fufd98rxxv2n8j9t3au733xlht3fga4gk8ks5mqq5tzsz"
|
||||||
|
const val XLM_NON_ACTIVATED_RECIPIENT_ADDRESS = "GAGMMENFDWASIHSVO4BPVIT3ZH3YNUIM4EJ6MUDJW46OVPNOJSQIJ22K"
|
||||||
|
const val XLM_ACTIVATED_RECIPIENT_ADDRESS = "GDKGS3UQFUNQY34P4SIAOKKGEA3NKHH6BSAXK4HIN3BZQQRBTITJLKL6"
|
||||||
|
const val XRP_NON_ACTIVATED_RECIPIENT_ADDRESS = "rvJfSnN6JzV3rhz1RRKDHnE6MYW28BaZG"
|
||||||
|
const val XRP_ACTIVATED_RECIPIENT_ADDRESS = "rN7n7otQDd6FczFgLdSqtcsAUxDkw6fzRH"
|
||||||
|
const val DOGECOIN_RECIPIENT_ADDRESS = "DJQR3bdhBKcFGMHX2BkMCkrMFApNWNzr6V"
|
||||||
|
const val DOGECOIN_ADDRESS = "DJ2TaZ5vvp3mBLugUpKjVM3pRBLi4uYaqz"
|
||||||
|
|
||||||
const val WAIT_UNTIL_TIMEOUT = 20_000L
|
const val WAIT_UNTIL_TIMEOUT = 20_000L
|
||||||
const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L
|
const val WAIT_UNTIL_TIMEOUT_LONG = 30_000L
|
||||||
|
|
@ -14,4 +38,7 @@ object TestConstants {
|
||||||
|
|
||||||
const val ALLURE_LABEL_NAME = "Owner"
|
const val ALLURE_LABEL_NAME = "Owner"
|
||||||
const val ALLURE_LABEL_VALUE = "Kaspresso"
|
const val ALLURE_LABEL_VALUE = "Kaspresso"
|
||||||
|
|
||||||
|
const val USER_TOKENS_API_SCENARIO = "user_tokens_api"
|
||||||
|
const val QUOTES_API_SCENARIO = "quotes_api"
|
||||||
}
|
}
|
||||||
|
|
@ -20,12 +20,12 @@ fun BaseTestCase.swipeVertical(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fun BaseTestCase.pullToRefresh() {
|
fun BaseTestCase.pullToRefresh(steps: Int = 1000) {
|
||||||
swipeVertical(
|
swipeVertical(
|
||||||
direction = SwipeDirection.DOWN,
|
direction = SwipeDirection.DOWN,
|
||||||
startHeightRatio = 0.2f,
|
startHeightRatio = 0.2f,
|
||||||
endHeightRatio = 0.8f,
|
endHeightRatio = 0.8f,
|
||||||
steps = 1000
|
steps = steps
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -115,4 +115,28 @@ fun BaseTestCase.checkMultiCurrencyMainScreen(
|
||||||
step("Assert 'Organize tokens' button is displayed") {
|
step("Assert 'Organize tokens' button is displayed") {
|
||||||
onMainScreen { organizeTokensButton().assertIsDisplayed() }
|
onMainScreen { organizeTokensButton().assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.assertActionButtonsForMultiCurrencyWallet(isEnabled: Boolean = true) {
|
||||||
|
if (isEnabled) {
|
||||||
|
step("Assert 'Buy' button is enabled") {
|
||||||
|
onMainScreen { buyButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is enabled") {
|
||||||
|
onMainScreen { swapButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is enabled") {
|
||||||
|
onMainScreen { sellButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
step("Assert 'Buy' button is not enabled") {
|
||||||
|
onMainScreen { buyButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is not enabled") {
|
||||||
|
onMainScreen { swapButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is not enabled") {
|
||||||
|
onMainScreen { sellButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -8,6 +8,7 @@ import com.tangem.screens.AlreadyUsedWalletDialogPageObject.requestSupportButton
|
||||||
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.thisIsMyWalletButton
|
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.thisIsMyWalletButton
|
||||||
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.title
|
import com.tangem.screens.AlreadyUsedWalletDialogPageObject.title
|
||||||
import com.tangem.screens.ScanWarningDialogPageObject
|
import com.tangem.screens.ScanWarningDialogPageObject
|
||||||
|
import com.tangem.screens.onActionIsUnavailableDialog
|
||||||
import com.tangem.screens.onFailedTransactionDialog
|
import com.tangem.screens.onFailedTransactionDialog
|
||||||
import io.qameta.allure.kotlin.Allure.step
|
import io.qameta.allure.kotlin.Allure.step
|
||||||
|
|
||||||
|
|
@ -63,4 +64,16 @@ fun checkAlreadyUsedWalletDialog() {
|
||||||
step("Assert 'Request support' button is displayed") {
|
step("Assert 'Request support' button is displayed") {
|
||||||
AlreadyUsedWalletDialogPageObject { requestSupportButton.isDisplayed() }
|
AlreadyUsedWalletDialogPageObject { requestSupportButton.isDisplayed() }
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.checkActionIsUnavailableDialog() {
|
||||||
|
step("Assert 'Action is unavailable' dialog title is displayed") {
|
||||||
|
onActionIsUnavailableDialog { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Action is unavailable' dialog text is displayed") {
|
||||||
|
onActionIsUnavailableDialog { text.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Action is unavailable' dialog 'Ok' button is displayed") {
|
||||||
|
onActionIsUnavailableDialog { okButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package com.tangem.scenarios
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.screens.onReceiveAssetsBottomSheet
|
||||||
|
import com.tangem.screens.onTokenReceiveQrCodeBottomSheet
|
||||||
|
import com.tangem.screens.onTokenReceiveWarningBottomSheet
|
||||||
|
import io.qameta.allure.kotlin.Allure.step
|
||||||
|
|
||||||
|
fun BaseTestCase.goToQrCodeBottomSheet() {
|
||||||
|
step("Assert 'Token receive warning' bottom sheet is displayed") {
|
||||||
|
onTokenReceiveWarningBottomSheet { bottomSheet.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Got it' button") {
|
||||||
|
onTokenReceiveWarningBottomSheet { gotItButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Click on 'Show QR code' button") {
|
||||||
|
onReceiveAssetsBottomSheet { showQrCodeButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.checkQrCodeBottomSheetScenario() {
|
||||||
|
step("Assert bottom sheet with QR code title is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert QR code is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { qrCode.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert address title is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { addressTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert address is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { address.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Copy' button is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { copyButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Share' button is displayed") {
|
||||||
|
onTokenReceiveQrCodeBottomSheet { shareButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
149
app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt
Normal file
149
app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt
Normal file
|
|
@ -0,0 +1,149 @@
|
||||||
|
package com.tangem.scenarios
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.screens.*
|
||||||
|
import com.tangem.screens.onMainScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.screens.onTokenDetailsScreen
|
||||||
|
import io.qameta.allure.kotlin.Allure.step
|
||||||
|
|
||||||
|
fun BaseTestCase.openSendScreen(tokenName: String, mockState: String = "") {
|
||||||
|
val scenarioState = mockState.ifEmpty { tokenName }
|
||||||
|
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = scenarioState)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = scenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenName'") {
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Send' button") {
|
||||||
|
onTokenDetailsScreen { sendButton().performClick() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.checkNetworkFeeBlock(currentFeeAmount: String, withFeeSelector: Boolean) {
|
||||||
|
step("Assert fee selector block is displayed") {
|
||||||
|
onSendConfirmScreen { feeSelectorBlock.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert fee selector icon is displayed") {
|
||||||
|
onSendConfirmScreen { feeSelectorIcon.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert fee selector title is displayed") {
|
||||||
|
onSendConfirmScreen { feeSelectorTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert fee selector tooltip icon is displayed") {
|
||||||
|
onSendConfirmScreen { feeSelectorTooltipIcon.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert fee amount = '$currentFeeAmount'") {
|
||||||
|
onSendConfirmScreen { feeAmount.assertTextContains(currentFeeAmount) }
|
||||||
|
}
|
||||||
|
if (withFeeSelector) {
|
||||||
|
step("Assert select fee icon is displayed") {
|
||||||
|
onSendConfirmScreen { selectFeeIcon.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.openSendConfirmScreen(
|
||||||
|
tokenName: String,
|
||||||
|
inputAmount: String,
|
||||||
|
recipientAddress: String
|
||||||
|
) {
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, inputAmount)
|
||||||
|
}
|
||||||
|
step("Type recipient address") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.openSendAddressScreen(
|
||||||
|
tokenName: String,
|
||||||
|
inputAmount: String
|
||||||
|
) {
|
||||||
|
step("Click on token with name: '$tokenName'") {
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Send' button") {
|
||||||
|
onTokenDetailsScreen { sendButton().performClick() }
|
||||||
|
}
|
||||||
|
step("Type '$inputAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(inputAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Send Address' container is displayed") {
|
||||||
|
onSendAddressScreen { container.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.checkScanQrScreen(emptyClipboard: Boolean = true) {
|
||||||
|
step("Assert 'Back' button is displayed") {
|
||||||
|
onScanQrScreen { backTopAppBarButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Flashlight' button is displayed") {
|
||||||
|
onScanQrScreen { flashlightButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Gallery' button is displayed") {
|
||||||
|
onScanQrScreen { galleryButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
if (!emptyClipboard) {
|
||||||
|
step("Assert 'Paste from clipboard' is displayed") {
|
||||||
|
onScanQrScreen { pasteFromClipboardButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.checkDestinationTagBlock(hint: String) {
|
||||||
|
step("Assert 'Destination Tag' title is displayed") {
|
||||||
|
onSendAddressScreen { destinationTagBlockTitle().assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Destination Tag' text is displayed") {
|
||||||
|
onSendAddressScreen { destinationTagBlockText.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Destination Tag' text field hint contains text '$hint'") {
|
||||||
|
onSendAddressScreen { destinationTagTextFieldHint.assertTextContains(hint) }
|
||||||
|
}
|
||||||
|
step("Assert 'Destination Tag' text field is displayed") {
|
||||||
|
onSendAddressScreen { destinationTagTextField.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Destination Tag' caution is displayed") {
|
||||||
|
onSendAddressScreen { destinationTagBlockCaution.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseTestCase.checkRecentAddressItem(address: String, description: String) {
|
||||||
|
step("Assert recent address '$address' is displayed") {
|
||||||
|
onSendAddressScreen {
|
||||||
|
recentAddressItem(address).assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert recent address item description is '$description'") {
|
||||||
|
onSendAddressScreen {
|
||||||
|
recentAddressItemDescription(description).assertTextContains(
|
||||||
|
description,
|
||||||
|
substring = true
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,56 @@
|
||||||
|
package com.tangem.scenarios
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.qameta.allure.kotlin.Allure.step
|
||||||
|
|
||||||
|
fun BaseTestCase.checkSendWarning(
|
||||||
|
title: String,
|
||||||
|
message: String,
|
||||||
|
isDisplayed: Boolean = true,
|
||||||
|
sendButtonIsDisabled: Boolean = isDisplayed,
|
||||||
|
) {
|
||||||
|
val assertDisplay = if (isDisplayed) "displayed" else "not displayed"
|
||||||
|
|
||||||
|
step("Assert 'Send confirm screen' is displayed") {
|
||||||
|
onSendConfirmScreen {
|
||||||
|
appBarTitle.assertIsDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert warning title is $assertDisplay") {
|
||||||
|
onSendConfirmScreen {
|
||||||
|
warningTitle(title).assertVisibility(isDisplayed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert warning icon is $assertDisplay") {
|
||||||
|
onSendConfirmScreen {
|
||||||
|
sendWarningIcon(message).assertVisibility(isDisplayed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert warning message is $assertDisplay") {
|
||||||
|
onSendConfirmScreen {
|
||||||
|
sendWarningMessage(message).assertVisibility(isDisplayed)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sendButtonIsDisabled)
|
||||||
|
step("Assert 'Send' button is disabled") {
|
||||||
|
onSendConfirmScreen {
|
||||||
|
sendButton.assertIsNotEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
step("Assert 'Send' button is enabled") {
|
||||||
|
onSendConfirmScreen {
|
||||||
|
sendButton.assertIsEnabled()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun KNode.assertVisibility(shouldBeDisplayed: Boolean) {
|
||||||
|
if (shouldBeDisplayed) {
|
||||||
|
assertIsDisplayed()
|
||||||
|
} else {
|
||||||
|
assertIsNotDisplayed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,40 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import com.tangem.core.ui.test.BaseDialogTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import com.tangem.common.ui.R as CommonUIR
|
||||||
|
|
||||||
|
class ActionIsUnavailableDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<ActionIsUnavailableDialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val dialogContainer: KNode = child {
|
||||||
|
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||||
|
}
|
||||||
|
|
||||||
|
val title: KNode = child {
|
||||||
|
hasTestTag(BaseDialogTestTags.TITLE)
|
||||||
|
hasText(getResourceString(CommonUIR.string.action_buttons_something_wrong_alert_title))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val text: KNode = child {
|
||||||
|
hasTestTag(BaseDialogTestTags.TEXT)
|
||||||
|
hasText(getResourceString(CommonUIR.string.action_buttons_something_wrong_alert_message))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val okButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
|
hasText(getResourceString(R.string.common_ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onActionIsUnavailableDialog(function: ActionIsUnavailableDialogPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -61,20 +61,10 @@ class BuyTokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProv
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val tokenAmountField: KNode = child {
|
|
||||||
hasParent(withTestTag(BuyTokenDetailsScreenTestTags.TOKEN_AMOUNT))
|
|
||||||
useUnmergedTree = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val recommendedTitle: KNode = child {
|
val recommendedTitle: KNode = child {
|
||||||
hasText(getResourceString(OnrampImplR.string.onramp_recommended_title))
|
hasText(getResourceString(OnrampImplR.string.onramp_recommended_title))
|
||||||
}
|
}
|
||||||
|
|
||||||
val bestRateIcon: KNode = child {
|
|
||||||
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_ICON)
|
|
||||||
useUnmergedTree = true
|
|
||||||
}
|
|
||||||
|
|
||||||
val bestRateTitle: KNode = child {
|
val bestRateTitle: KNode = child {
|
||||||
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE)
|
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.test.BaseBottomSheetTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||||
|
import androidx.compose.ui.test.hasText as withText
|
||||||
|
import com.tangem.feature.qrscanning.impl.R as QrScanningImplR
|
||||||
|
|
||||||
|
class CameraAccessDeniedBottomSheetPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<CameraAccessDeniedBottomSheetPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val title: KNode = child {
|
||||||
|
hasText(getResourceString(QrScanningImplR.string.qr_scanner_camera_denied_title))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val subtitle: KNode = child {
|
||||||
|
hasText(getResourceString(QrScanningImplR.string.qr_scanner_camera_denied_text))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val settingsButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.qr_scanner_camera_denied_settings_button)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectFromGalleryButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.qr_scanner_camera_denied_gallery_button)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val closeButton: KNode = child {
|
||||||
|
hasAnyChild(withText(getResourceString(R.string.common_close)))
|
||||||
|
hasTestTag(BaseBottomSheetTestTags.ACTION_BUTTON)
|
||||||
|
hasAnyChild(withTestTag(BaseBottomSheetTestTags.ACTION_ICON))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onCameraAccessDeniedBottomSheet(function: CameraAccessDeniedBottomSheetPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -17,6 +17,14 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
hasTestTag(BaseDialogTestTags.CONTAINER)
|
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val title: KNode = child {
|
||||||
|
hasTestTag(BaseDialogTestTags.TITLE)
|
||||||
|
}
|
||||||
|
|
||||||
|
val text: KNode = child {
|
||||||
|
hasTestTag(BaseDialogTestTags.TEXT)
|
||||||
|
}
|
||||||
|
|
||||||
val cancelButton: KNode = child {
|
val cancelButton: KNode = child {
|
||||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
hasText(getResourceString(R.string.common_cancel))
|
hasText(getResourceString(R.string.common_cancel))
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import com.tangem.core.ui.test.BaseDialogTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import com.tangem.common.ui.R as CommonUIR
|
||||||
|
|
||||||
|
class OperationIsUnavailableDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<OperationIsUnavailableDialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val text: KNode = child {
|
||||||
|
hasTestTag(BaseDialogTestTags.TEXT)
|
||||||
|
hasText(getResourceString(CommonUIR.string.token_button_unavailability_generic_description))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val okButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
|
hasText(getResourceString(R.string.common_ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onOperationIsUnavailableDialog(function: OperationIsUnavailableDialogPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -0,0 +1,26 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import com.kaspersky.components.kautomator.component.text.UiButton
|
||||||
|
import com.kaspersky.components.kautomator.component.text.UiTextView
|
||||||
|
import com.kaspersky.components.kautomator.screen.UiScreen
|
||||||
|
|
||||||
|
object PermissionDialogPageObject : UiScreen<PermissionDialogPageObject>() {
|
||||||
|
|
||||||
|
override val packageName = "com.android.permissioncontroller"
|
||||||
|
|
||||||
|
val allowWhileUsingButton = UiButton {
|
||||||
|
withId(this@PermissionDialogPageObject.packageName,"permission_allow_foreground_only_button")
|
||||||
|
}
|
||||||
|
|
||||||
|
val allowOnlyThisTimeButton = UiButton {
|
||||||
|
withId(this@PermissionDialogPageObject.packageName, "permission_allow_one_time_button")
|
||||||
|
}
|
||||||
|
|
||||||
|
val doNotAllowButton = UiButton {
|
||||||
|
withId(this@PermissionDialogPageObject.packageName, "permission_deny_button")
|
||||||
|
}
|
||||||
|
|
||||||
|
val permissionMessage = UiTextView {
|
||||||
|
withId(this@PermissionDialogPageObject.packageName, "permission_message")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import com.tangem.core.ui.test.QrCodeScreenTestTags
|
||||||
|
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
|
class ScanQrPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<ScanQrPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val backTopAppBarButton: KNode = child {
|
||||||
|
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val sendTitle: KNode = child {
|
||||||
|
hasTestTag(TopAppBarTestTags.TITLE)
|
||||||
|
hasText(getResourceString(R.string.common_send))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val flashlightButton: KNode = child {
|
||||||
|
hasTestTag(QrCodeScreenTestTags.FLASHLIGHT_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val galleryButton: KNode = child {
|
||||||
|
hasTestTag(QrCodeScreenTestTags.GALLERY_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun scanText(tokenName: String): KNode = child {
|
||||||
|
hasText(getResourceString(R.string.send_qrcode_scan_info, tokenName))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val pasteFromClipboardButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
|
hasText(getResourceString(R.string.wallet_connect_paste_from_clipboard))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onScanQrScreen(function: ScanQrPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -3,21 +3,22 @@ package com.tangem.screens
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
import com.tangem.core.ui.test.*
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.KNode
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
|
||||||
class WalletConnectScanQrPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class SellPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<WalletConnectScanQrPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<SellPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
val pasteFromClipboardButton: KNode = child {
|
val title: KNode = child {
|
||||||
hasTestTag(BaseButtonTestTags.TEXT)
|
hasTestTag(TopAppBarTestTags.TITLE)
|
||||||
hasText(getResourceString(R.string.wallet_connect_paste_from_clipboard))
|
hasText(getResourceString(R.string.common_sell))
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun BaseTestCase.onWalletConnectScanQrScreen(function: WalletConnectScanQrPageObject.() -> Unit) =
|
internal fun BaseTestCase.onSellScreen(function: SellPageObject.() -> Unit) =
|
||||||
onComposeScreen(composeTestRule, function)
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -9,18 +9,135 @@ import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.KNode
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||||
|
import androidx.compose.ui.test.hasText as withText
|
||||||
|
import com.tangem.core.ui.R as CoreUiR
|
||||||
|
|
||||||
class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<SendAddressPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<SendAddressPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
val container: KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.CONTAINER)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val addressTextFieldTitle: KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD_TITLE)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
val addressTextField: KNode = child {
|
val addressTextField: KNode = child {
|
||||||
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD)
|
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val addressPasteButton: KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.ADDRESS_PASTE_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val qrButton: KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.QR_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val clearTextFieldButton: KNode = child {
|
||||||
|
hasContentDescription(getResourceString(CoreUiR.string.common_close))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val resolvedAddress: KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.RESOLVED_ADDRESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
val recentAddressesTitle: KNode = child {
|
||||||
|
hasText(getResourceString(CoreUiR.string.send_recent_transactions))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val myWalletsTitle: KNode = child {
|
||||||
|
hasText(getResourceString(CoreUiR.string.send_recipient_wallets_title))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recentAddressItem(recipientAddress: String, isMyWallet: Boolean = false): KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_ITEM)
|
||||||
|
hasAnyChild(withTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_ICON))
|
||||||
|
hasAnyDescendant(withText(recipientAddress))
|
||||||
|
hasAnyDescendant(withTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_TEXT))
|
||||||
|
useUnmergedTree = true
|
||||||
|
if (isMyWallet) {
|
||||||
|
hasAnySibling(withText(getResourceString(CoreUiR.string.send_recipient_wallets_title)))
|
||||||
|
hasAnyDescendant(withText(getResourceString(CoreUiR.string.manage_tokens_network_selector_wallet)))
|
||||||
|
} else {
|
||||||
|
hasAnySibling(withText(getResourceString(CoreUiR.string.send_recent_transactions)))
|
||||||
|
hasAnyDescendant(withTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_TRANSACTION_ICON))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recentAddressItemDescription(recipientAddress: String): KNode = recentAddressItem(recipientAddress).child {
|
||||||
|
hasParent(withTestTag(SendAddressScreenTestTags.RECENT_ADDRESS_TEXT))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun destinationTagBlockTitle(isMemoCorrectOrEmpty: Boolean = true): KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_TEXT_FIELD_TITLE)
|
||||||
|
useUnmergedTree = true
|
||||||
|
if(isMemoCorrectOrEmpty) {
|
||||||
|
hasText(getResourceString(CoreUiR.string.send_destination_tag_field))
|
||||||
|
} else {
|
||||||
|
hasText(getResourceString(CoreUiR.string.send_memo_destination_tag_error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val destinationTagTextField: KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_TEXT_FIELD)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val destinationTagTextFieldHint: KNode = child {
|
||||||
|
hasParent(withTestTag(SendAddressScreenTestTags.DESTINATION_TAG_TEXT_FIELD))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val destinationTagBlockText: KNode = child {
|
||||||
|
hasText(
|
||||||
|
getResourceString(CoreUiR.string.send_recipient_memo_footer_v2) + "\n" +
|
||||||
|
getResourceString(CoreUiR.string.send_recipient_memo_footer_v2_highlighted)
|
||||||
|
)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val destinationTagBlockCaution: KNode = child {
|
||||||
|
hasText(
|
||||||
|
getResourceString(
|
||||||
|
CoreUiR.string.send_recipient_memo_footer_v2,
|
||||||
|
CoreUiR.string.send_recipient_memo_footer_v2_highlighted
|
||||||
|
),
|
||||||
|
substring = true
|
||||||
|
)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val destinationTagPasteButton: KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_PASTE_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val clearDestinationTagTextFieldButton: KNode = child {
|
||||||
|
hasTestTag(SendAddressScreenTestTags.DESTINATION_TAG_CLEAR_TEXT_FIELD_BUTTON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
val nextButton: KNode = child {
|
val nextButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
|
hasAnyDescendant(withText(getResourceString(R.string.common_next)))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val continueButton: KNode = child {
|
||||||
hasTestTag(BaseButtonTestTags.TEXT)
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
hasText(getResourceString(R.string.common_next))
|
hasText(getResourceString(R.string.common_continue))
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -2,36 +2,55 @@ package com.tangem.screens
|
||||||
|
|
||||||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.test.*
|
||||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
import com.tangem.wallet.R
|
||||||
import com.tangem.core.ui.test.NotificationTestTags
|
|
||||||
import com.tangem.core.ui.test.SendConfirmScreenTestTags
|
|
||||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
import io.github.kakaocup.compose.node.element.KNode
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
import androidx.compose.ui.test.hasText as withText
|
import androidx.compose.ui.test.hasText as withText
|
||||||
import com.tangem.common.ui.R as CommonUiR
|
import androidx.compose.ui.test.hasTestTag as withTestTag
|
||||||
|
import com.tangem.core.ui.R as CoreUiR
|
||||||
|
|
||||||
class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
ComposeScreen<SendConfirmPageObject>(semanticsProvider = semanticsProvider) {
|
ComposeScreen<SendConfirmPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
val title: KNode = child {
|
val appBarTitle: KNode = child {
|
||||||
hasTestTag(TopAppBarTestTags.TITLE)
|
hasTestTag(TopAppBarTestTags.TITLE)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
val sendButton: KNode = child {
|
val sendButton: KNode = child {
|
||||||
hasTestTag(BaseButtonTestTags.TEXT)
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
hasText(getResourceString(R.string.common_send))
|
hasAnyDescendant(withText(getResourceString(R.string.common_send)))
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val primaryAmount: KNode = child {
|
||||||
|
hasTestTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
val minimumSendAmountErrorTitle: KNode = child {
|
val secondaryAmount: KNode = child {
|
||||||
|
hasTestTag(BaseAmountBlockTestTags.SECONDARY_AMOUNT)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun leaveDepositButton(amount: String): KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
|
hasAnyDescendant(withText(getResourceString(R.string.send_notification_leave_button, amount)))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun reduceAmountButton(amount: String): KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
|
hasAnyDescendant(withText(getResourceString(R.string.send_notification_reduce_by, amount)))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun warningTitle(title: String): KNode = child {
|
||||||
hasTestTag(NotificationTestTags.TITLE)
|
hasTestTag(NotificationTestTags.TITLE)
|
||||||
hasText(getResourceString(CommonUiR.string.send_notification_invalid_amount_title))
|
hasText(title)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -40,34 +59,76 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun minimumSendAmountErrorIcon(amount: String): KNode = child {
|
fun sendWarningIcon(message: String): KNode = child {
|
||||||
hasAnySibling(
|
|
||||||
withText(
|
|
||||||
getResourceString(
|
|
||||||
CommonUiR.string.send_notification_invalid_minimum_amount_text,
|
|
||||||
amount,
|
|
||||||
amount,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
hasTestTag(NotificationTestTags.ICON)
|
hasTestTag(NotificationTestTags.ICON)
|
||||||
|
hasAnySibling(withText(message))
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
fun minimumSendAmountErrorMessage(
|
fun sendWarningMessage(
|
||||||
amount: String,
|
message: String,
|
||||||
): KNode = child {
|
): KNode = child {
|
||||||
hasTestTag(NotificationTestTags.MESSAGE)
|
hasTestTag(NotificationTestTags.MESSAGE)
|
||||||
hasText(
|
hasText(message)
|
||||||
getResourceString(
|
|
||||||
CommonUiR.string.send_notification_invalid_minimum_amount_text,
|
|
||||||
amount,
|
|
||||||
amount,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun warningMessage(messageResId: Int): KNode = child {
|
||||||
|
hasTestTag(NotificationTestTags.MESSAGE)
|
||||||
|
hasText(getResourceString(messageResId))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun warningIcon(message: String): KNode = child {
|
||||||
|
hasTestTag(NotificationTestTags.ICON)
|
||||||
|
hasAnySibling(withText(message))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun recipientAddress(recipientAddress: String): KNode = child {
|
||||||
|
hasTestTag(SendConfirmScreenTestTags.RECIPIENT_ADDRESS)
|
||||||
|
hasText(recipientAddress)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val blockchainAddress: KNode = child {
|
||||||
|
hasTestTag(SendConfirmScreenTestTags.BLOCKCHAIN_ADDRESS)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val feeSelectorBlock: KNode = child {
|
||||||
|
hasTestTag(FeeSelectorBlockTestTags.SELECTOR_BLOCK)
|
||||||
|
}
|
||||||
|
|
||||||
|
val feeSelectorIcon: KNode = child {
|
||||||
|
hasTestTag(FeeSelectorBlockTestTags.ICON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val feeSelectorTitle: KNode = child {
|
||||||
|
hasTestTag(FeeSelectorBlockTestTags.TITLE)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val feeSelectorTooltipIcon: KNode = child {
|
||||||
|
hasTestTag(FeeSelectorBlockTestTags.TOOLTIP_ICON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val selectFeeIcon: KNode = child {
|
||||||
|
hasTestTag(FeeSelectorBlockTestTags.SELECT_FEE_ICON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val feeAmount: KNode = child {
|
||||||
|
hasParent(withTestTag(FeeSelectorBlockTestTags.FEE_AMOUNT))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val refreshButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
|
hasText(getResourceString(CoreUiR.string.warning_button_refresh))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) =
|
internal fun BaseTestCase.onSendConfirmScreen(function: SendConfirmPageObject.() -> Unit) =
|
||||||
|
|
|
||||||
|
|
@ -38,6 +38,16 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val equivalentInputAmount: KNode = child {
|
||||||
|
hasTestTag(SendScreenTestTags.EQUIVALENT_INPUT_AMOUNT)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val exchangeIcon: KNode = child {
|
||||||
|
hasTestTag(SendScreenTestTags.EXCHANGE_ICON)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
val tokenName: KNode = child {
|
val tokenName: KNode = child {
|
||||||
hasTestTag(SendScreenTestTags.TOKEN_NAME)
|
hasTestTag(SendScreenTestTags.TOKEN_NAME)
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
|
|
@ -64,6 +74,12 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
useUnmergedTree = true
|
useUnmergedTree = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
val continueButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.TEXT)
|
||||||
|
hasText(getResourceString(SendR.string.common_continue))
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
internal fun BaseTestCase.onSendScreen(function: SendPageObject.() -> Unit) =
|
internal fun BaseTestCase.onSendScreen(function: SendPageObject.() -> Unit) =
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,35 @@
|
||||||
|
package com.tangem.screens
|
||||||
|
|
||||||
|
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||||
|
import com.tangem.core.ui.test.BaseDialogTestTags
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.ComposeScreen.Companion.onComposeScreen
|
||||||
|
import io.github.kakaocup.compose.node.element.KNode
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import com.tangem.common.ui.R as CommonUIR
|
||||||
|
|
||||||
|
class SwapIsNotSupportedDialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||||
|
ComposeScreen<SwapIsNotSupportedDialogPageObject>(semanticsProvider = semanticsProvider) {
|
||||||
|
|
||||||
|
fun text(currencyName: String): KNode = child {
|
||||||
|
hasTestTag(BaseDialogTestTags.TEXT)
|
||||||
|
hasText(
|
||||||
|
getResourceString(
|
||||||
|
CommonUIR.string.token_button_unavailability_reason_not_exchangeable,
|
||||||
|
currencyName
|
||||||
|
)
|
||||||
|
)
|
||||||
|
useUnmergedTree = true
|
||||||
|
}
|
||||||
|
|
||||||
|
val okButton: KNode = child {
|
||||||
|
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||||
|
hasText(getResourceString(R.string.common_ok))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun BaseTestCase.onSwapIsNotSupportedDialog(function: SwapIsNotSupportedDialogPageObject.() -> Unit) =
|
||||||
|
onComposeScreen(composeTestRule, function)
|
||||||
|
|
@ -97,25 +97,31 @@ class TokenDetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvide
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(ExperimentalTestApi::class)
|
@OptIn(ExperimentalTestApi::class)
|
||||||
val swapButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
fun receiveButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||||
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||||
|
hasText(getResourceString(R.string.common_receive))
|
||||||
|
}
|
||||||
|
|
||||||
|
@OptIn(ExperimentalTestApi::class)
|
||||||
|
fun swapButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||||
hasText(getResourceString(R.string.common_swap))
|
hasText(getResourceString(R.string.common_swap))
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTestApi::class)
|
@OptIn(ExperimentalTestApi::class)
|
||||||
val sellButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
fun sellButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||||
hasText(getResourceString(R.string.common_sell))
|
hasText(getResourceString(R.string.common_sell))
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTestApi::class)
|
@OptIn(ExperimentalTestApi::class)
|
||||||
val buyButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
fun buyButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||||
hasText(getResourceString(R.string.common_buy))
|
hasText(getResourceString(R.string.common_buy))
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(ExperimentalTestApi::class)
|
@OptIn(ExperimentalTestApi::class)
|
||||||
val sendButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
fun sendButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||||
hasText(getResourceString(R.string.common_send))
|
hasText(getResourceString(R.string.common_send))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,23 @@ package com.tangem.tests
|
||||||
|
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS
|
import com.tangem.common.constants.TestConstants.CARDANO_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
import com.tangem.common.extensions.clickWithAssertion
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.common.extensions.pullToRefresh
|
import com.tangem.common.extensions.pullToRefresh
|
||||||
|
import com.tangem.common.ui.R
|
||||||
import com.tangem.common.utils.resetWireMockScenarioState
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
import com.tangem.common.utils.setWireMockScenarioState
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
import com.tangem.scenarios.openMainScreen
|
import com.tangem.scenarios.openMainScreen
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
import com.tangem.scenarios.synchronizeAddresses
|
import com.tangem.scenarios.synchronizeAddresses
|
||||||
import com.tangem.screens.*
|
import com.tangem.screens.onMainScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.screens.onTokenDetailsScreen
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
import io.qameta.allure.kotlin.AllureId
|
import io.qameta.allure.kotlin.AllureId
|
||||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -26,28 +35,18 @@ class BlockchainTest : BaseTestCase() {
|
||||||
val validSendAmount = "10"
|
val validSendAmount = "10"
|
||||||
val minAmount = "ADA 1.00"
|
val minAmount = "ADA 1.00"
|
||||||
val address = CARDANO_ADDRESS
|
val address = CARDANO_ADDRESS
|
||||||
val scenarioName = "user_tokens_api"
|
|
||||||
val scenarioState = "Cardano"
|
val invalidAmountTitle = getResourceString(R.string.send_notification_invalid_amount_title)
|
||||||
|
val invalidAmountMessage =
|
||||||
|
getResourceString(R.string.send_notification_invalid_minimum_amount_text, minAmount, minAmount)
|
||||||
|
|
||||||
setupHooks(
|
setupHooks(
|
||||||
additionalAfterSection = {
|
additionalAfterSection = {
|
||||||
resetWireMockScenarioState(scenarioName)
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
}
|
}
|
||||||
).run {
|
).run {
|
||||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
openSendScreen(tokenName)
|
||||||
}
|
|
||||||
step("Open 'Main Screen'") {
|
|
||||||
openMainScreen()
|
|
||||||
}
|
|
||||||
step("Synchronize addresses") {
|
|
||||||
synchronizeAddresses()
|
|
||||||
}
|
|
||||||
step("Click on token with name: '$tokenName'") {
|
|
||||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
|
||||||
}
|
|
||||||
step("Click on 'Send' button") {
|
|
||||||
onTokenDetailsScreen { sendButton.performClick() }
|
|
||||||
}
|
}
|
||||||
step("Type '$errorSendAmount' in input text field") {
|
step("Type '$errorSendAmount' in input text field") {
|
||||||
onSendScreen {
|
onSendScreen {
|
||||||
|
|
@ -64,47 +63,30 @@ class BlockchainTest : BaseTestCase() {
|
||||||
step("Click on 'Next' button") {
|
step("Click on 'Next' button") {
|
||||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Assert 'Invalid amount' error title is displayed") {
|
step("Assert 'Invalid amount warning' is displayed") {
|
||||||
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsDisplayed() }
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
step("Assert 'Invalid amount' error icon is displayed") {
|
step("Click on 'Amount' field") {
|
||||||
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsDisplayed() }
|
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||||
}
|
|
||||||
step("Assert 'Invalid amount' error message is displayed") {
|
|
||||||
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Press system 'Back' button") {
|
|
||||||
device.uiDevice.pressBack()
|
|
||||||
}
|
|
||||||
step("Assert address text field is displayed") {
|
|
||||||
onSendAddressScreen { addressTextField.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Press system 'Back' button") {
|
|
||||||
device.uiDevice.pressBack()
|
|
||||||
}
|
}
|
||||||
step("Type '$validSendAmount' in input text field") {
|
step("Type '$validSendAmount' in input text field") {
|
||||||
onSendScreen {
|
onSendScreen {
|
||||||
amountInputTextField.performClick()
|
amountInputTextField.performClick()
|
||||||
amountInputTextField.performTextReplacement(errorSendAmount)
|
amountInputTextField.performTextReplacement(validSendAmount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
step("Click on 'Next' button") {
|
step("Click on 'Continue' button") {
|
||||||
onSendScreen { nextButton.clickWithAssertion() }
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Assert address text field is displayed") {
|
step("Assert 'Invalid amount warning' is not displayed") {
|
||||||
onSendAddressScreen { addressTextField.assertIsDisplayed() }
|
checkSendWarning(
|
||||||
}
|
title = invalidAmountTitle,
|
||||||
step("Click on 'Next' button") {
|
message = invalidAmountMessage,
|
||||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
isDisplayed = false
|
||||||
}
|
)
|
||||||
step("Assert 'Invalid amount' error title is not displayed") {
|
|
||||||
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsNotDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Invalid amount' error icon is not displayed") {
|
|
||||||
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsNotDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Invalid amount' error message is not displayed") {
|
|
||||||
onSendConfirmScreen { minimumSendAmountErrorMessage(minAmount).assertIsNotDisplayed() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -136,10 +118,16 @@ class BlockchainTest : BaseTestCase() {
|
||||||
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||||
}
|
}
|
||||||
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoErrorState'") {
|
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoErrorState'") {
|
||||||
setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoErrorState)
|
setWireMockScenarioState(
|
||||||
|
scenarioName = rippleAccountInfoScenarioName,
|
||||||
|
state = rippleAccountInfoErrorState
|
||||||
|
)
|
||||||
}
|
}
|
||||||
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesErrorState'") {
|
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesErrorState'") {
|
||||||
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesErrorState)
|
setWireMockScenarioState(
|
||||||
|
scenarioName = rippleAccountLinesScenarioName,
|
||||||
|
state = rippleAccountLinesErrorState
|
||||||
|
)
|
||||||
}
|
}
|
||||||
step("Open 'Main Screen'") {
|
step("Open 'Main Screen'") {
|
||||||
openMainScreen()
|
openMainScreen()
|
||||||
|
|
@ -169,10 +157,16 @@ class BlockchainTest : BaseTestCase() {
|
||||||
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||||
}
|
}
|
||||||
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoStartedState'") {
|
step("Set WireMock scenario: '$rippleAccountInfoScenarioName' to state: '$rippleAccountInfoStartedState'") {
|
||||||
setWireMockScenarioState(scenarioName = rippleAccountInfoScenarioName, state = rippleAccountInfoStartedState)
|
setWireMockScenarioState(
|
||||||
|
scenarioName = rippleAccountInfoScenarioName,
|
||||||
|
state = rippleAccountInfoStartedState
|
||||||
|
)
|
||||||
}
|
}
|
||||||
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesStartedState'") {
|
step("Set WireMock scenario: '$rippleAccountLinesScenarioName' to state: '$rippleAccountLinesStartedState'") {
|
||||||
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesStartedState)
|
setWireMockScenarioState(
|
||||||
|
scenarioName = rippleAccountLinesScenarioName,
|
||||||
|
state = rippleAccountLinesStartedState
|
||||||
|
)
|
||||||
}
|
}
|
||||||
step("Pull to refresh") {
|
step("Pull to refresh") {
|
||||||
pullToRefresh()
|
pullToRefresh()
|
||||||
|
|
|
||||||
|
|
@ -70,7 +70,7 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
val otherCurrenciesTitle = "Other currencies"
|
val otherCurrenciesTitle = "Other currencies"
|
||||||
val australianDollar = "AUD"
|
val australianDollar = "AUD"
|
||||||
val fiatAmount = "1"
|
val fiatAmount = "1"
|
||||||
val tokenAmount = "~488.24938338 POL"
|
val tokenAmount = "POL 488.24938338"
|
||||||
val scenarioName = "payment_methods"
|
val scenarioName = "payment_methods"
|
||||||
|
|
||||||
step("Reset WireMock scenario '$scenarioName'") {
|
step("Reset WireMock scenario '$scenarioName'") {
|
||||||
|
|
@ -98,10 +98,8 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
step("Write fiat amount = '$fiatAmount'") {
|
step("Write fiat amount = '$fiatAmount'") {
|
||||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||||
}
|
}
|
||||||
step("Assert token amount = '$tokenAmount'") {
|
step("Assert offer token amount = '$tokenAmount'") {
|
||||||
onBuyTokenDetailsScreen {
|
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||||
tokenAmountField.assertTextContains(tokenAmount)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
step("Fiat currency icon is displayed") {
|
step("Fiat currency icon is displayed") {
|
||||||
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
|
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
|
||||||
|
|
@ -129,10 +127,8 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
fiatAmountTextField.assertTextContains(australianDollar + fiatAmount)
|
fiatAmountTextField.assertTextContains(australianDollar + fiatAmount)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
step("Assert token amount = '$tokenAmount'") {
|
step("Assert offer token amount = '$tokenAmount'") {
|
||||||
onBuyTokenDetailsScreen {
|
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||||
tokenAmountField.assertTextContains(tokenAmount)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -145,7 +141,7 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
val tokenTitle = "Polygon"
|
val tokenTitle = "Polygon"
|
||||||
val euro = "EUR"
|
val euro = "EUR"
|
||||||
val fiatAmount = "1"
|
val fiatAmount = "1"
|
||||||
val tokenAmount = "~488.24938338 POL"
|
val tokenAmount = "POL 488.24938338"
|
||||||
val scenarioName = "payment_methods"
|
val scenarioName = "payment_methods"
|
||||||
|
|
||||||
step("Reset WireMock scenario '$scenarioName'") {
|
step("Reset WireMock scenario '$scenarioName'") {
|
||||||
|
|
@ -182,18 +178,12 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
step("Assert fiat amount = '$fiatAmount'") {
|
step("Assert fiat amount = '$fiatAmount'") {
|
||||||
onBuyTokenDetailsScreen { fiatAmountTextField.assertTextContains(euro + fiatAmount) }
|
onBuyTokenDetailsScreen { fiatAmountTextField.assertTextContains(euro + fiatAmount) }
|
||||||
}
|
}
|
||||||
step("Assert token amount = '$tokenAmount'") {
|
step("Assert offer token amount = '$tokenAmount'") {
|
||||||
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
|
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||||
}
|
|
||||||
step("Click on 'Continue' button") {
|
|
||||||
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
|
|
||||||
}
|
}
|
||||||
step("Assert 'Recommended' title is displayed") {
|
step("Assert 'Recommended' title is displayed") {
|
||||||
onBuyTokenDetailsScreen { recommendedTitle.clickWithAssertion() }
|
onBuyTokenDetailsScreen { recommendedTitle.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Assert 'Best rate' icon is displayed") {
|
|
||||||
onBuyTokenDetailsScreen { bestRateIcon.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Best rate' title is displayed") {
|
step("Assert 'Best rate' title is displayed") {
|
||||||
onBuyTokenDetailsScreen { bestRateTitle.assertIsDisplayed() }
|
onBuyTokenDetailsScreen { bestRateTitle.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
|
|
@ -316,7 +306,7 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
val paymentMethod = "Invoice Revolut Pay"
|
val paymentMethod = "Invoice Revolut Pay"
|
||||||
val fiatAmount = "1"
|
val fiatAmount = "1"
|
||||||
val providerNameMercuryo = "Mercuryo"
|
val providerNameMercuryo = "Mercuryo"
|
||||||
val tokenAmount = "~488.24938338 POL"
|
val tokenAmount = "POL 488.24938338"
|
||||||
val scenarioName = "payment_methods"
|
val scenarioName = "payment_methods"
|
||||||
|
|
||||||
step("Reset WireMock scenario '$scenarioName'") {
|
step("Reset WireMock scenario '$scenarioName'") {
|
||||||
|
|
@ -344,11 +334,8 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
step("Write fiat amount = '$fiatAmount'") {
|
step("Write fiat amount = '$fiatAmount'") {
|
||||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||||
}
|
}
|
||||||
step("Assert token amount = '$tokenAmount'") {
|
step("Assert offer token amount = '$tokenAmount'") {
|
||||||
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
|
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||||
}
|
|
||||||
step("Click on 'Continue' button") {
|
|
||||||
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
|
|
||||||
}
|
}
|
||||||
step("Assert 'Provider block' is displayed") {
|
step("Assert 'Provider block' is displayed") {
|
||||||
onBuyTokenDetailsScreen { providerName.assertIsDisplayed() }
|
onBuyTokenDetailsScreen { providerName.assertIsDisplayed() }
|
||||||
|
|
@ -365,9 +352,6 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
step("Assert 'Provider' bottom sheet subtitle is displayed") {
|
step("Assert 'Provider' bottom sheet subtitle is displayed") {
|
||||||
onSelectProviderBottomSheet { subtitle.assertIsDisplayed() }
|
onSelectProviderBottomSheet { subtitle.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
step("Assert 'Best rate' icon is displayed") {
|
|
||||||
onSelectProviderBottomSheet { bestRateIcon.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Best rate' title is displayed") {
|
step("Assert 'Best rate' title is displayed") {
|
||||||
onSelectProviderBottomSheet { bestRateTitle.assertIsDisplayed() }
|
onSelectProviderBottomSheet { bestRateTitle.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
|
|
@ -406,7 +390,7 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
val invoiceRevolutPay = "Invoice Revolut Pay"
|
val invoiceRevolutPay = "Invoice Revolut Pay"
|
||||||
val sepa = "Sepa"
|
val sepa = "Sepa"
|
||||||
val fiatAmount = "1"
|
val fiatAmount = "1"
|
||||||
val tokenAmount = "~488.24938338 POL"
|
val tokenAmount = "POL 488.24938338"
|
||||||
val scenarioName = "payment_methods"
|
val scenarioName = "payment_methods"
|
||||||
|
|
||||||
step("Reset WireMock scenario '$scenarioName'") {
|
step("Reset WireMock scenario '$scenarioName'") {
|
||||||
|
|
@ -434,11 +418,8 @@ class BuyTokenTest : BaseTestCase() {
|
||||||
step("Write fiat amount = '$fiatAmount'") {
|
step("Write fiat amount = '$fiatAmount'") {
|
||||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||||
}
|
}
|
||||||
step("Assert token amount = '$tokenAmount'") {
|
step("Assert offer token amount = '$tokenAmount'") {
|
||||||
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
|
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||||
}
|
|
||||||
step("Click on 'Continue' button") {
|
|
||||||
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
|
|
||||||
}
|
}
|
||||||
step("Assert 'Provider block' is displayed") {
|
step("Assert 'Provider block' is displayed") {
|
||||||
onBuyTokenDetailsScreen { providerName.assertIsDisplayed() }
|
onBuyTokenDetailsScreen { providerName.assertIsDisplayed() }
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,6 @@ import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
import com.tangem.tap.domain.sdk.mocks.MockProvider
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.qameta.allure.kotlin.Allure
|
|
||||||
import io.qameta.allure.kotlin.AllureId
|
import io.qameta.allure.kotlin.AllureId
|
||||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
@ -73,7 +72,7 @@ class FeedbackTest : BaseTestCase() {
|
||||||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Click 'Send' button") {
|
step("Click 'Send' button") {
|
||||||
onTokenDetailsScreen { sendButton.performClick() }
|
onTokenDetailsScreen { sendButton().performClick() }
|
||||||
}
|
}
|
||||||
step("Type '$sendAmount' in input text field") {
|
step("Type '$sendAmount' in input text field") {
|
||||||
onSendScreen {
|
onSendScreen {
|
||||||
|
|
@ -130,7 +129,7 @@ class FeedbackTest : BaseTestCase() {
|
||||||
MockProvider.resetEmulateError()
|
MockProvider.resetEmulateError()
|
||||||
}
|
}
|
||||||
).run {
|
).run {
|
||||||
Allure.step("Click on 'Accept' button") {
|
step("Click on 'Accept' button") {
|
||||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Set scanning error") {
|
step("Set scanning error") {
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,7 @@ class SwapTokenTest : BaseTestCase() {
|
||||||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
step("Click on 'Swap' button") {
|
step("Click on 'Swap' button") {
|
||||||
onTokenDetailsScreen { swapButton.performClick() }
|
onTokenDetailsScreen { swapButton().performClick() }
|
||||||
}
|
}
|
||||||
step("Close 'Stories' screen") {
|
step("Close 'Stories' screen") {
|
||||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||||
|
|
@ -131,7 +131,7 @@ class SwapTokenTest : BaseTestCase() {
|
||||||
disableMobileData()
|
disableMobileData()
|
||||||
}
|
}
|
||||||
step("Click on 'Swap' button") {
|
step("Click on 'Swap' button") {
|
||||||
onTokenDetailsScreen { swapButton.performClick() }
|
onTokenDetailsScreen { swapButton().performClick() }
|
||||||
}
|
}
|
||||||
step("Close 'Stories' screen") {
|
step("Close 'Stories' screen") {
|
||||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||||
|
|
@ -175,7 +175,7 @@ class SwapTokenTest : BaseTestCase() {
|
||||||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
step("Click on 'Swap' button") {
|
step("Click on 'Swap' button") {
|
||||||
onTokenDetailsScreen { swapButton.performClick() }
|
onTokenDetailsScreen { swapButton().performClick() }
|
||||||
}
|
}
|
||||||
step("Close 'Stories' screen") {
|
step("Close 'Stories' screen") {
|
||||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,6 @@
|
||||||
package com.tangem.tests
|
package com.tangem.tests
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||||
import com.tangem.common.extensions.clickWithAssertion
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
|
@ -8,7 +9,7 @@ import com.tangem.common.utils.setClipboardText
|
||||||
import com.tangem.scenarios.*
|
import com.tangem.scenarios.*
|
||||||
import com.tangem.screens.onWalletConnectBottomSheet
|
import com.tangem.screens.onWalletConnectBottomSheet
|
||||||
import com.tangem.screens.onWalletConnectDetailsBottomSheet
|
import com.tangem.screens.onWalletConnectDetailsBottomSheet
|
||||||
import com.tangem.screens.onWalletConnectScanQrScreen
|
import com.tangem.screens.onScanQrScreen
|
||||||
import com.tangem.screens.onWalletConnectScreen
|
import com.tangem.screens.onWalletConnectScreen
|
||||||
import com.tangem.wallet.BuildConfig
|
import com.tangem.wallet.BuildConfig
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
|
@ -196,8 +197,14 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
val dAppName = "React App"
|
val dAppName = "React App"
|
||||||
val context = device.context
|
val context = device.context
|
||||||
val deepLinkUri = getWcUri()
|
val deepLinkUri = getWcUri()
|
||||||
|
val packageName = BuildConfig.APPLICATION_ID
|
||||||
|
val permissionName = Manifest.permission.CAMERA
|
||||||
|
|
||||||
setupHooks().run {
|
setupHooks(
|
||||||
|
additionalBeforeSection = {
|
||||||
|
device.uiDevice.executeShellCommand("pm grant $packageName $permissionName")
|
||||||
|
},
|
||||||
|
).run {
|
||||||
step("Set URI to clipboard") {
|
step("Set URI to clipboard") {
|
||||||
setClipboardText(context, deepLinkUri)
|
setClipboardText(context, deepLinkUri)
|
||||||
}
|
}
|
||||||
|
|
@ -214,7 +221,7 @@ class WalletConnectTest : BaseTestCase() {
|
||||||
onWalletConnectScreen { newConnectionButton.performClick() }
|
onWalletConnectScreen { newConnectionButton.performClick() }
|
||||||
}
|
}
|
||||||
step("CLick 'Paste from clipboard' button") {
|
step("CLick 'Paste from clipboard' button") {
|
||||||
onWalletConnectScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
|
onScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
|
||||||
}
|
}
|
||||||
step("Check 'Wallet Connect' bottom sheet") {
|
step("Check 'Wallet Connect' bottom sheet") {
|
||||||
waitForIdle()
|
waitForIdle()
|
||||||
|
|
|
||||||
|
|
@ -2,14 +2,19 @@ package com.tangem.tests.actionButtons
|
||||||
|
|
||||||
import androidx.compose.ui.test.longClick
|
import androidx.compose.ui.test.longClick
|
||||||
import com.tangem.common.BaseTestCase
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.annotations.ApiEnv
|
||||||
|
import com.tangem.common.annotations.ApiEnvConfig
|
||||||
import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS
|
import com.tangem.common.constants.TestConstants.BITCOIN_ADDRESS
|
||||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||||
import com.tangem.common.extensions.clickWithAssertion
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
import com.tangem.common.utils.assertClipboardTextEquals
|
import com.tangem.common.extensions.*
|
||||||
import com.tangem.common.utils.clearClipboard
|
import com.tangem.common.utils.*
|
||||||
import com.tangem.scenarios.openMainScreen
|
import com.tangem.datasource.api.common.config.ApiConfig
|
||||||
import com.tangem.scenarios.synchronizeAddresses
|
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||||
|
import com.tangem.scenarios.*
|
||||||
import com.tangem.screens.*
|
import com.tangem.screens.*
|
||||||
|
import com.tangem.tap.domain.sdk.mocks.MockContent
|
||||||
|
import com.tangem.tap.domain.sdk.mocks.content.TwinsMockContent
|
||||||
import dagger.hilt.android.testing.HiltAndroidTest
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
import io.qameta.allure.kotlin.AllureId
|
import io.qameta.allure.kotlin.AllureId
|
||||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
|
@ -121,7 +126,6 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
||||||
@Test
|
@Test
|
||||||
fun clickOnBuyButtonTest() {
|
fun clickOnBuyButtonTest() {
|
||||||
val tokenTitle = "Bitcoin"
|
val tokenTitle = "Bitcoin"
|
||||||
val tokenSymbol = "BTC"
|
|
||||||
|
|
||||||
setupHooks().run {
|
setupHooks().run {
|
||||||
step("Open 'Main Screen'") {
|
step("Open 'Main Screen'") {
|
||||||
|
|
@ -160,12 +164,6 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
||||||
step("Assert fiat currency icon is displayed") {
|
step("Assert fiat currency icon is displayed") {
|
||||||
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
|
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
step("Assert token amount field is displayed") {
|
|
||||||
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenSymbol, substring = true) }
|
|
||||||
}
|
|
||||||
step("Assert 'Continue' button") {
|
|
||||||
onBuyTokenDetailsScreen { continueButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -281,41 +279,18 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
||||||
step("Click on 'Receive' button") {
|
step("Click on 'Receive' button") {
|
||||||
onTokenActionsBottomSheet { receiveButton.performClick() }
|
onTokenActionsBottomSheet { receiveButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert 'Token receive warning' bottom sheet is displayed") {
|
step("Go to QR code bottom sheet") {
|
||||||
waitForIdle()
|
|
||||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
onTokenReceiveWarningBottomSheet {
|
goToQrCodeBottomSheet()
|
||||||
bottomSheet.assertIsDisplayed()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
step("Click on 'Got it' button") {
|
step("Check QR code bottom sheet") {
|
||||||
onTokenReceiveWarningBottomSheet { gotItButton.performClick() }
|
checkQrCodeBottomSheetScenario()
|
||||||
}
|
|
||||||
step("Click on 'Show QR code' button") {
|
|
||||||
onReceiveAssetsBottomSheet { showQrCodeButton.clickWithAssertion() }
|
|
||||||
}
|
|
||||||
step("Assert bottom sheet with QR code title is displayed") {
|
|
||||||
onTokenReceiveQrCodeBottomSheet { title.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert QR code is displayed") {
|
|
||||||
onTokenReceiveQrCodeBottomSheet { qrCode.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert address title is displayed") {
|
|
||||||
onTokenReceiveQrCodeBottomSheet { addressTitle.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert address is displayed") {
|
|
||||||
onTokenReceiveQrCodeBottomSheet { address.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Copy' button is displayed") {
|
|
||||||
onTokenReceiveQrCodeBottomSheet { copyButton.assertIsDisplayed() }
|
|
||||||
}
|
|
||||||
step("Assert 'Share' button is displayed") {
|
|
||||||
onTokenReceiveQrCodeBottomSheet { shareButton.assertIsDisplayed() }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ApiEnv(ApiEnvConfig(ApiConfig.ID.MoonPay, ApiEnvironment.PROD))
|
||||||
@AllureId("85")
|
@AllureId("85")
|
||||||
@DisplayName("Action buttons (long tap): check 'Sell' button")
|
@DisplayName("Action buttons (long tap): check 'Sell' button")
|
||||||
@Test
|
@Test
|
||||||
|
|
@ -342,10 +317,10 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
step("Assert 'Receive' button is displayed") {
|
step("Assert 'Sell' button is displayed") {
|
||||||
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
|
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
|
||||||
}
|
}
|
||||||
step("Click on 'Receive' button") {
|
step("Click on 'Sell' button") {
|
||||||
onTokenActionsBottomSheet { sellButton.performClick() }
|
onTokenActionsBottomSheet { sellButton.performClick() }
|
||||||
}
|
}
|
||||||
step("Assert Chrome Browser is opened") {
|
step("Assert Chrome Browser is opened") {
|
||||||
|
|
@ -391,4 +366,251 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@AllureId("895")
|
||||||
|
@DisplayName("Action buttons: check blockchain information by click on 'Buy' button")
|
||||||
|
@Test
|
||||||
|
fun checkClickOnBuyButtonOnMainTest() {
|
||||||
|
val cardType: MockContent = TwinsMockContent
|
||||||
|
val cardName = "Twin"
|
||||||
|
val tokenTitle = "Bitcoin"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen' on '$cardName' card") {
|
||||||
|
openMainScreen(mockContent = cardType, isTwinsCard = true)
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' button is displayed") {
|
||||||
|
onMainScreen { buyButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Buy' button") {
|
||||||
|
onMainScreen { buyButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Click on 'Confirm' button in 'Dialog'") {
|
||||||
|
waitForIdle()
|
||||||
|
onDialog { confirmButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert top app bar title contains '$tokenTitle'") {
|
||||||
|
onBuyTokenDetailsScreen { topBarTitle.assertTextContains("Buy $tokenTitle") }
|
||||||
|
}
|
||||||
|
step("Assert fiat currency text field is displayed") {
|
||||||
|
onBuyTokenDetailsScreen { fiatAmountTextField.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert fiat currency icon is displayed") {
|
||||||
|
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiEnv(ApiEnvConfig(ApiConfig.ID.MoonPay, ApiEnvironment.PROD))
|
||||||
|
@AllureId("4395")
|
||||||
|
@DisplayName("Action buttons (main screen): click on buttons with success response")
|
||||||
|
@Test
|
||||||
|
fun clickOnActionButtonsWithSuccessResponseTest() {
|
||||||
|
val tokenTitle = "Ethereum"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' button is displayed") {
|
||||||
|
onMainScreen { buyButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Buy' button") {
|
||||||
|
onMainScreen { buyButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' screen title is displayed") {
|
||||||
|
onBuyTokenScreen { topAppBarTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert token with title: '$tokenTitle' is displayed") {
|
||||||
|
onBuyTokenScreen { tokenWithTitleAndFiatAmount(tokenTitle).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Press 'Back' button") {
|
||||||
|
device.uiDevice.pressBack()
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is displayed") {
|
||||||
|
onMainScreen { swapButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Swap' button") {
|
||||||
|
onMainScreen { swapButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Click on close button on stories screen") {
|
||||||
|
onSwapStoriesScreen { closeButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' token screen title is displayed") {
|
||||||
|
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Press 'Back' button") {
|
||||||
|
device.uiDevice.pressBack()
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is displayed") {
|
||||||
|
onMainScreen { sellButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Sell' button") {
|
||||||
|
onMainScreen { sellButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' token screen title is displayed") {
|
||||||
|
onSellScreen { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4396")
|
||||||
|
@DisplayName("Action buttons (main screen): click on buttons without data")
|
||||||
|
@Test
|
||||||
|
fun clickOnActionButtonsWithoutDataTest() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
enableWiFi()
|
||||||
|
enableMobileData()
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Turn off internet") {
|
||||||
|
disableWiFi()
|
||||||
|
disableMobileData()
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' button is displayed") {
|
||||||
|
onMainScreen { buyButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Buy' button") {
|
||||||
|
onMainScreen { buyButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Check 'Action is unavailable' dialog") {
|
||||||
|
checkActionIsUnavailableDialog()
|
||||||
|
}
|
||||||
|
step("Click on 'Ok' button") {
|
||||||
|
onDialog { okButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is displayed") {
|
||||||
|
onMainScreen { swapButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Swap' button") {
|
||||||
|
onMainScreen { swapButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Check 'Action is unavailable' dialog") {
|
||||||
|
checkActionIsUnavailableDialog()
|
||||||
|
}
|
||||||
|
step("Click on 'Ok' button") {
|
||||||
|
onDialog { okButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is displayed") {
|
||||||
|
onMainScreen { sellButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Sell' button") {
|
||||||
|
onMainScreen { sellButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Check 'Action is unavailable' dialog") {
|
||||||
|
checkActionIsUnavailableDialog()
|
||||||
|
}
|
||||||
|
step("Click on 'Ok' button") {
|
||||||
|
onDialog { okButton.performClick() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4398")
|
||||||
|
@DisplayName("Action buttons (main screen): click on buttons with error response")
|
||||||
|
@Test
|
||||||
|
fun clickOnActionButtonsWithErrorResponseTest() {
|
||||||
|
val scenarioName = "express_api_assets"
|
||||||
|
val scenarioState = "Error"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(scenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName, scenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' button is displayed") {
|
||||||
|
onMainScreen { buyButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Buy' button") {
|
||||||
|
onMainScreen { buyButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Check 'Action is unavailable' dialog") {
|
||||||
|
checkActionIsUnavailableDialog()
|
||||||
|
}
|
||||||
|
step("Click on 'Ok' button") {
|
||||||
|
onDialog { okButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is displayed") {
|
||||||
|
onMainScreen { swapButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Swap' button") {
|
||||||
|
onMainScreen { swapButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Check 'Action is unavailable' dialog") {
|
||||||
|
checkActionIsUnavailableDialog()
|
||||||
|
}
|
||||||
|
step("Click on 'Ok' button") {
|
||||||
|
onDialog { okButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is displayed") {
|
||||||
|
onMainScreen { sellButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Sell' button") {
|
||||||
|
onMainScreen { sellButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Check 'Action is unavailable' dialog") {
|
||||||
|
checkActionIsUnavailableDialog()
|
||||||
|
}
|
||||||
|
step("Click on 'Ok' button") {
|
||||||
|
onDialog { okButton.performClick() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("3642")
|
||||||
|
@DisplayName("Action buttons (main screen): check buttons state")
|
||||||
|
@Test
|
||||||
|
fun checkButtonsStateTest() {
|
||||||
|
val scenarioName = "user_tokens_api"
|
||||||
|
val scenarioState = "EmptyTokensList"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(scenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Assert action buttons is not enabled") {
|
||||||
|
assertActionButtonsForMultiCurrencyWallet(isEnabled = false)
|
||||||
|
}
|
||||||
|
step("Reset Wiremock scenario: '$scenarioName'") {
|
||||||
|
resetWireMockScenarioState(scenarioName)
|
||||||
|
}
|
||||||
|
step("Perform pull to refresh") {
|
||||||
|
pullToRefresh(steps = 10)
|
||||||
|
waitForIdle()
|
||||||
|
}
|
||||||
|
step("Assert action buttons is enabled") {
|
||||||
|
assertActionButtonsForMultiCurrencyWallet(isEnabled = true)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||||
|
}
|
||||||
|
step("Perform pull to refresh") {
|
||||||
|
pullToRefresh(steps = 10)
|
||||||
|
waitForIdle()
|
||||||
|
}
|
||||||
|
step("Assert action buttons is not enabled") {
|
||||||
|
assertActionButtonsForMultiCurrencyWallet(isEnabled = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -0,0 +1,255 @@
|
||||||
|
package com.tangem.tests.actionButtons
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||||
|
import com.tangem.common.extensions.SwipeDirection
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.extensions.swipeVertical
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkQrCodeBottomSheetScenario
|
||||||
|
import com.tangem.scenarios.goToQrCodeBottomSheet
|
||||||
|
import com.tangem.scenarios.openMainScreen
|
||||||
|
import com.tangem.scenarios.synchronizeAddresses
|
||||||
|
import com.tangem.screens.*
|
||||||
|
import com.tangem.screens.onMainScreen
|
||||||
|
import com.tangem.screens.onSwapStoriesScreen
|
||||||
|
import com.tangem.screens.onSwapTokenScreen
|
||||||
|
import com.tangem.screens.onTokenDetailsScreen
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class TokenDetailsScreenActionButtonsTest : BaseTestCase() {
|
||||||
|
|
||||||
|
@AllureId("594")
|
||||||
|
@DisplayName("Action buttons (token details screen): validate UI")
|
||||||
|
@Test
|
||||||
|
fun actionButtonsValidateUiTest() {
|
||||||
|
val tokenTitle = "Bitcoin"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Receive' button is displayed") {
|
||||||
|
onTokenDetailsScreen { receiveButton().assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' button is displayed") {
|
||||||
|
onTokenDetailsScreen { buyButton().assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Send' button is displayed") {
|
||||||
|
onTokenDetailsScreen { sendButton().assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is displayed") {
|
||||||
|
onTokenDetailsScreen { swapButton().assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is displayed") {
|
||||||
|
onTokenDetailsScreen { sellButton().assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("593")
|
||||||
|
@DisplayName("Action buttons (token details screen): check buttons state")
|
||||||
|
@Test
|
||||||
|
fun checkActionButtonsStateTest() {
|
||||||
|
val tokenTitle = "Bitcoin"
|
||||||
|
val actionButtonIsNotDimmed = "Action button is not dimmed"
|
||||||
|
val actionButtonIsDimmed = "Action button is dimmed"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Receive' button is not dimmed") {
|
||||||
|
onTokenDetailsScreen { receiveButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
|
||||||
|
}
|
||||||
|
step("Assert 'Buy' button is not dimmed") {
|
||||||
|
onTokenDetailsScreen { buyButton().assertContentDescriptionEquals(actionButtonIsNotDimmed) }
|
||||||
|
}
|
||||||
|
step("Assert 'Send' button is dimmed") {
|
||||||
|
onTokenDetailsScreen { sendButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is dimmed") {
|
||||||
|
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||||
|
}
|
||||||
|
step("Assert 'Sell' button is dimmed") {
|
||||||
|
onTokenDetailsScreen { sellButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4459")
|
||||||
|
@DisplayName("Action buttons (token details screen): check 'Swap' button (success)")
|
||||||
|
@Test
|
||||||
|
fun checkSwapButtonSuccessTest() {
|
||||||
|
val tokenTitle = "Ethereum"
|
||||||
|
val tokenSymbol = "ETH"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||||
|
}
|
||||||
|
step("Click on 'Swap' button") {
|
||||||
|
onTokenDetailsScreen { swapButton().performClick() }
|
||||||
|
}
|
||||||
|
step("Close 'Stories' screen") {
|
||||||
|
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' screen title is displayed") {
|
||||||
|
onSwapTokenScreen { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert token symbol: '$tokenSymbol' is displayed") {
|
||||||
|
onSwapTokenScreen { tokenSymbol(tokenSymbol).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4460")
|
||||||
|
@DisplayName("Action buttons (token details screen): check 'Swap' button (provider error)")
|
||||||
|
@Test
|
||||||
|
fun checkSwapButtonProviderErrorTest() {
|
||||||
|
val tokenTitle = "POL (ex-MATIC)"
|
||||||
|
val actionButtonIsDimmed = "Action button is dimmed"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Swipe up") {
|
||||||
|
swipeVertical(SwipeDirection.UP)
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is dimmed") {
|
||||||
|
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||||
|
}
|
||||||
|
step("Click on 'Swap' button") {
|
||||||
|
onTokenDetailsScreen { swapButton().performClick() }
|
||||||
|
}
|
||||||
|
step("Assert swapping $tokenTitle is not supported dialog text is displayed") {
|
||||||
|
onSwapIsNotSupportedDialog { text(tokenTitle).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Ok' button is displayed") {
|
||||||
|
onSwapIsNotSupportedDialog { okButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Ok' button") {
|
||||||
|
onSwapIsNotSupportedDialog { okButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is dimmed") {
|
||||||
|
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4461")
|
||||||
|
@DisplayName("Action buttons (token details screen): check 'Swap' button (Express error)")
|
||||||
|
@Test
|
||||||
|
fun checkSwapButtonExpressErrorTest() {
|
||||||
|
val tokenTitle = "Polygon"
|
||||||
|
val actionButtonIsDimmed = "Action button is dimmed"
|
||||||
|
val scenarioName = "express_api_assets"
|
||||||
|
val scenarioState = "Error"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(scenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName, scenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Swipe up") {
|
||||||
|
swipeVertical(SwipeDirection.UP)
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is dimmed") {
|
||||||
|
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||||
|
}
|
||||||
|
step("Click on 'Swap' button") {
|
||||||
|
onTokenDetailsScreen { swapButton().performClick() }
|
||||||
|
}
|
||||||
|
step("Assert operation is unavailable dialog text is displayed") {
|
||||||
|
onOperationIsUnavailableDialog { text.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Ok' button is displayed") {
|
||||||
|
onOperationIsUnavailableDialog { okButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Ok' button") {
|
||||||
|
onOperationIsUnavailableDialog { okButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Swap' button is dimmed") {
|
||||||
|
onTokenDetailsScreen { swapButton().assertContentDescriptionEquals(actionButtonIsDimmed) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("3590")
|
||||||
|
@DisplayName("Action buttons (token details screen): validate UI")
|
||||||
|
@Test
|
||||||
|
fun checkReceiveButtonTest() {
|
||||||
|
val tokenTitle = "Bitcoin"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenTitle'") {
|
||||||
|
waitForIdle()
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenTitle).performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Receive' button is displayed") {
|
||||||
|
onTokenDetailsScreen { receiveButton().performClick() }
|
||||||
|
}
|
||||||
|
step("Go to QR code bottom sheet") {
|
||||||
|
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||||
|
goToQrCodeBottomSheet()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Check QR code bottom sheet") {
|
||||||
|
checkQrCodeBottomSheetScenario()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,137 @@
|
||||||
|
package com.tangem.tests.send.addressScreen
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.DOGECOIN_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.ETHEREUM_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.openMainScreen
|
||||||
|
import com.tangem.scenarios.openSendAddressScreen
|
||||||
|
import com.tangem.scenarios.synchronizeAddresses
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class MyWalletsBlockTest : BaseTestCase() {
|
||||||
|
private val txHistoryScenarioName = "dogecoin_tx_history"
|
||||||
|
|
||||||
|
@AllureId("4579")
|
||||||
|
@DisplayName("Send (address screen): click on 'My wallet address'")
|
||||||
|
@Test
|
||||||
|
fun clickOnMyWalletAddressTest() {
|
||||||
|
val tokenName = "Dogecoin"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val txHistoryScenarioState = "OutgoingTransaction"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(txHistoryScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$txHistoryScenarioName' to state: '$txHistoryScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = txHistoryScenarioName, state = txHistoryScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Click on 'My wallet' address") {
|
||||||
|
onSendAddressScreen {
|
||||||
|
recentAddressItem(recipientAddress = DOGECOIN_ADDRESS, isMyWallet = true).performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert recipient address is displayed on confirm screen") {
|
||||||
|
onSendConfirmScreen { recipientAddress(DOGECOIN_ADDRESS).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4599")
|
||||||
|
@DisplayName("Send (address screen): 'My wallets' block is not displayed for account networks")
|
||||||
|
@Test
|
||||||
|
fun myWalletsBlockForAccountNetworksNotDisplayedTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val sendAmount = "1"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Assert 'My wallets' title is not displayed") {
|
||||||
|
onSendAddressScreen { myWalletsTitle.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert recent address item is not displayed") {
|
||||||
|
onSendAddressScreen { recentAddressItem(ETHEREUM_ADDRESS).assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4737")
|
||||||
|
@DisplayName("Send (address screen): assert 'My wallet address is correct'")
|
||||||
|
@Test
|
||||||
|
fun checkMyWalletsBlockTest() {
|
||||||
|
val tokenName = "Dogecoin"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val txHistoryScenarioState = "OutgoingTransaction"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(txHistoryScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$txHistoryScenarioName' to state: '$txHistoryScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = txHistoryScenarioName, state = txHistoryScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Assert 'My wallets' title is displayed") {
|
||||||
|
onSendAddressScreen { myWalletsTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'My wallet' address is displayed") {
|
||||||
|
onSendAddressScreen {
|
||||||
|
recentAddressItem(recipientAddress = DOGECOIN_ADDRESS, isMyWallet = true).performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,330 @@
|
||||||
|
package com.tangem.tests.send.addressScreen
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.DOGECOIN_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.SwipeDirection
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.extensions.swipeVertical
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.*
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class RecentBlockTest : BaseTestCase() {
|
||||||
|
private val txHistoryScenarioName = "dogecoin_tx_history"
|
||||||
|
private val descriptionForWalletTransaction = "DOGE 0.0192, 11/10/2025, 1:46 PM"
|
||||||
|
|
||||||
|
@AllureId("4005")
|
||||||
|
@DisplayName("Send (address screen): check address history")
|
||||||
|
@Test
|
||||||
|
fun sendAddressHistoryTest() {
|
||||||
|
val tokenName = "Dogecoin"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val txHistoryScenarioState = "OutgoingTransaction"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(txHistoryScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$txHistoryScenarioName' to state: '$txHistoryScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = txHistoryScenarioName, state = txHistoryScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Click on previous address") {
|
||||||
|
onSendAddressScreen { recentAddressItem(DOGECOIN_ADDRESS).performClick() }
|
||||||
|
}
|
||||||
|
step("Assert recipient address is displayed") {
|
||||||
|
onSendConfirmScreen { recipientAddress(DOGECOIN_ADDRESS).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Press system 'Back' button") {
|
||||||
|
device.uiDevice.pressBack()
|
||||||
|
}
|
||||||
|
step("Assert address text field contains correct recipient address") {
|
||||||
|
onSendAddressScreen { addressTextField.assertTextContains(DOGECOIN_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Assert 'Recent' title is not displayed") {
|
||||||
|
onSendAddressScreen { recentAddressesTitle.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
step("Click on 'Cross' button") {
|
||||||
|
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert recipient text field is empty") {
|
||||||
|
onSendAddressScreen { addressTextField.assertTextEquals("") }
|
||||||
|
}
|
||||||
|
step("Assert 'Recent' title is displayed") {
|
||||||
|
onSendAddressScreen { recentAddressesTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is disabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4597")
|
||||||
|
@DisplayName("Send (address screen): check 'Recent' block, transaction history doesn't supported")
|
||||||
|
@Test
|
||||||
|
fun recentBlockTransactionHistoryDoesNotSupportedTest() {
|
||||||
|
val tokenName = "Polkadot"
|
||||||
|
val sendAmount = "1"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$sendAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(sendAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Send Address' container is displayed") {
|
||||||
|
onSendAddressScreen { container.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Recent' title is not displayed") {
|
||||||
|
onSendAddressScreen { recentAddressesTitle.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4596")
|
||||||
|
@DisplayName("Send (address screen): check 'Recent' block, transaction history is empty")
|
||||||
|
@Test
|
||||||
|
fun recentBlockTransactionHistoryEmptyTest() {
|
||||||
|
val tokenName = "POL (ex-MATIC)"
|
||||||
|
val sendAmount = "1"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Assert 'Recent' title is not displayed") {
|
||||||
|
onSendAddressScreen { recentAddressesTitle.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert recent address item is not displayed") {
|
||||||
|
onSendAddressScreen { recentAddressItem(DOGECOIN_ADDRESS).assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4596")
|
||||||
|
@DisplayName("Send (address screen): check 'Recent' block, transaction history does not load")
|
||||||
|
@Test
|
||||||
|
fun recentBlockTransactionHistoryDoesNotLoadTest() {
|
||||||
|
val tokenName = "Dogecoin"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val txHistoryScenarioState = "Error"
|
||||||
|
val scenarioState = "Dogecoin"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(txHistoryScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = scenarioState)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = scenarioState)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$txHistoryScenarioName' to state: '$txHistoryScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = txHistoryScenarioName, state = txHistoryScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Assert 'Recent' title is not displayed") {
|
||||||
|
onSendAddressScreen { recentAddressesTitle.assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert recent address item is not displayed") {
|
||||||
|
onSendAddressScreen { recentAddressItem(DOGECOIN_ADDRESS).assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4572")
|
||||||
|
@DisplayName("Send (address screen): check 'Recent' block for >10 transactions")
|
||||||
|
@Test
|
||||||
|
fun checkRecentBlockMoreThanTenTransactionsTest() {
|
||||||
|
val tokenName = "Dogecoin"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val txHistoryScenarioState = "11OutgoingTransactions"
|
||||||
|
val recipientAddressBase = "DJ2TaZ5vvp3mBLugUpKjVM3pRBLi4uYaq"
|
||||||
|
val shortenedRecipientAddress = "DJ2TaZ5vvp3mBLugU...Li4uYaq123456789b"
|
||||||
|
val descriptionForTransaction = "DOGE 0.10, 11/10/2025, 1:46 PM"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(txHistoryScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$txHistoryScenarioName' to state: '$txHistoryScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = txHistoryScenarioName, state = txHistoryScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Assert 'Recent' title is displayed") {
|
||||||
|
onSendAddressScreen { recentAddressesTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Check recent address item №1") {
|
||||||
|
checkRecentAddressItem(address = DOGECOIN_ADDRESS, description = descriptionForWalletTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item №2") {
|
||||||
|
checkRecentAddressItem(address = shortenedRecipientAddress, description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item №3") {
|
||||||
|
checkRecentAddressItem(address = recipientAddressBase + "k", description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item №4") {
|
||||||
|
checkRecentAddressItem(address = recipientAddressBase + "c", description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item №5") {
|
||||||
|
checkRecentAddressItem(address = recipientAddressBase + "d", description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item №6") {
|
||||||
|
checkRecentAddressItem(address = recipientAddressBase + "e", description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Swipe up") {
|
||||||
|
swipeVertical(SwipeDirection.UP)
|
||||||
|
}
|
||||||
|
step("Check recent address item №7") {
|
||||||
|
checkRecentAddressItem(address = recipientAddressBase + "f", description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item №8") {
|
||||||
|
checkRecentAddressItem(address = recipientAddressBase + "g", description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item №9") {
|
||||||
|
checkRecentAddressItem(address = recipientAddressBase + "o", description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item №10") {
|
||||||
|
checkRecentAddressItem(address = recipientAddressBase + "p", description = descriptionForTransaction)
|
||||||
|
}
|
||||||
|
step("Assert recent address item №11 is not displayed") {
|
||||||
|
onSendAddressScreen { recentAddressItem(recipientAddressBase + "a").assertIsNotDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on recent address item №1") {
|
||||||
|
onSendAddressScreen { recentAddressItem(DOGECOIN_ADDRESS).performClick() }
|
||||||
|
}
|
||||||
|
step("Assert recipient address is displayed on 'Send Confirm' screen") {
|
||||||
|
onSendConfirmScreen { recipientAddress(DOGECOIN_ADDRESS).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4676")
|
||||||
|
@DisplayName("Send (address screen): check 'Recent' block for <10 transactions")
|
||||||
|
@Test
|
||||||
|
fun checkRecentBlockLessThanTenTransactionsTest() {
|
||||||
|
val tokenName = "Dogecoin"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val txHistoryScenarioState = "2OutgoingTransactions"
|
||||||
|
val descriptionForWalletTransaction2 = "DOGE 0.0192, 11/09/2025, 3:18 PM"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(txHistoryScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$USER_TOKENS_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = USER_TOKENS_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$QUOTES_API_SCENARIO' to state: '$tokenName'") {
|
||||||
|
setWireMockScenarioState(scenarioName = QUOTES_API_SCENARIO, state = tokenName)
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$txHistoryScenarioName' to state: '$txHistoryScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = txHistoryScenarioName, state = txHistoryScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Assert 'Recent' title is displayed") {
|
||||||
|
onSendAddressScreen { recentAddressesTitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Check recent address item") {
|
||||||
|
checkRecentAddressItem(address = DOGECOIN_ADDRESS, description = descriptionForWalletTransaction)
|
||||||
|
}
|
||||||
|
step("Check recent address item") {
|
||||||
|
checkRecentAddressItem(address = DOGECOIN_ADDRESS, description = descriptionForWalletTransaction2)
|
||||||
|
}
|
||||||
|
step("Click on recent address item") {
|
||||||
|
onSendAddressScreen { recentAddressItem(DOGECOIN_ADDRESS).performClick() }
|
||||||
|
}
|
||||||
|
step("Assert recipient address is displayed on 'Send Confirm' screen") {
|
||||||
|
onSendConfirmScreen { recipientAddress(DOGECOIN_ADDRESS).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,583 @@
|
||||||
|
package com.tangem.tests.send.addressScreen
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import androidx.compose.ui.test.hasText
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.ENS_ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.ENS_ETHEREUM_RECIPIENT_SHORTENED_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.ENS_NAME
|
||||||
|
import com.tangem.common.constants.TestConstants.ETHEREUM_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.XRP_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.XRP_X_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.XRP_X_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.XRP_X_RECIPIENT_ADDRESS_WITH_TAG
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.clearClipboard
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.common.utils.setClipboardText
|
||||||
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.scenarios.*
|
||||||
|
import com.tangem.screens.*
|
||||||
|
import com.tangem.wallet.BuildConfig
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class SendAddressScreenTest : BaseTestCase() {
|
||||||
|
|
||||||
|
@AllureId("4004")
|
||||||
|
@DisplayName("Send (address screen): check destination tag")
|
||||||
|
@Test
|
||||||
|
fun sendDestinationTagTest() {
|
||||||
|
val tokenName = "XRP Ledger"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val correctMemo = "123"
|
||||||
|
val invalidMemo = "hz"
|
||||||
|
val xrpRecipientAddress = XRP_RECIPIENT_ADDRESS
|
||||||
|
val userTokensScenarioName = "user_tokens_api"
|
||||||
|
val userTokensScenarioState = "XRP"
|
||||||
|
val context = device.context
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(userTokensScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Set clipboard text") {
|
||||||
|
setClipboardText(context, correctMemo)
|
||||||
|
}
|
||||||
|
step("Assert 'Destination Tag' title is displayed") {
|
||||||
|
onSendAddressScreen { destinationTagBlockTitle().assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Destination Tag' text is displayed") {
|
||||||
|
onSendAddressScreen { destinationTagBlockText.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("CLick on 'Paste' button") {
|
||||||
|
onSendAddressScreen { destinationTagPasteButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is disabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(xrpRecipientAddress) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
step("Click on 'Clear text field button'") {
|
||||||
|
onSendAddressScreen { clearDestinationTagTextFieldButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type invalid memo in input text field") {
|
||||||
|
onSendAddressScreen { destinationTagTextField.performTextReplacement(invalidMemo) }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid memo' title is displayed") {
|
||||||
|
onSendAddressScreen { destinationTagBlockTitle(isMemoCorrectOrEmpty = false).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is disabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4543")
|
||||||
|
@DisplayName("Send (address screen): check address field")
|
||||||
|
@Test
|
||||||
|
fun sendAddressFieldTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
val invalidAddress = "s"
|
||||||
|
val walletAddress = ETHEREUM_ADDRESS
|
||||||
|
val context = device.context
|
||||||
|
val recipient = getResourceString(R.string.send_recipient)
|
||||||
|
val notAValidAddress = getResourceString(R.string.send_recipient_address_error)
|
||||||
|
val sameAsWalletAddress = getResourceString(R.string.send_error_address_same_as_wallet)
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
clearClipboard()
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Set clipboard text") {
|
||||||
|
setClipboardText(context, recipientAddress)
|
||||||
|
}
|
||||||
|
step("Click on 'Paste' button") {
|
||||||
|
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert address text field contains correct recipient address") {
|
||||||
|
onSendAddressScreen { addressTextField.assertTextContains(recipientAddress) }
|
||||||
|
}
|
||||||
|
step("Assert address text field title is displayed") {
|
||||||
|
onSendAddressScreen { addressTextFieldTitle.assertTextContains(recipient) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
step("Set clipboard text") {
|
||||||
|
setClipboardText(context, invalidAddress)
|
||||||
|
}
|
||||||
|
step("Click on 'Cross' button") {
|
||||||
|
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Paste' button") {
|
||||||
|
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert address text field contains invalid address") {
|
||||||
|
onSendAddressScreen { addressTextField.assertTextContains(invalidAddress) }
|
||||||
|
}
|
||||||
|
step("Assert invalid address text field title is displayed") {
|
||||||
|
onSendAddressScreen { addressTextFieldTitle.assertTextContains(notAValidAddress) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is disabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
step("Set clipboard text") {
|
||||||
|
setClipboardText(context, walletAddress)
|
||||||
|
}
|
||||||
|
step("Click on 'Cross' button") {
|
||||||
|
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Paste' button") {
|
||||||
|
onSendAddressScreen { addressPasteButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert address text field contains invalid address") {
|
||||||
|
onSendAddressScreen { addressTextField.assertTextContains(walletAddress) }
|
||||||
|
}
|
||||||
|
step("Assert 'Address is the same as wallet address' error title is displayed") {
|
||||||
|
onSendAddressScreen { addressTextFieldTitle.assertTextContains(sameAsWalletAddress) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is disabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4004")
|
||||||
|
@DisplayName("Send (address screen): check ENS name")
|
||||||
|
@Test
|
||||||
|
fun sendEnsNameTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val ensName = ENS_NAME
|
||||||
|
val invalidEnsName = "l"
|
||||||
|
val ensAddress = ENS_ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
val ensShortenedAddress = ENS_ETHEREUM_RECIPIENT_SHORTENED_ADDRESS
|
||||||
|
val scenarioName = "eth_call_api"
|
||||||
|
val scenarioState = "EnsName"
|
||||||
|
val notAValidAddress = getResourceString(R.string.send_recipient_address_error)
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(scenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenName'") {
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenName).performClick() }
|
||||||
|
}
|
||||||
|
step("Click on 'Send' button") {
|
||||||
|
onTokenDetailsScreen { sendButton().performClick() }
|
||||||
|
}
|
||||||
|
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||||
|
}
|
||||||
|
step("Type '$sendAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(sendAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type ENS name: '$ensName' in text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(ensName) }
|
||||||
|
}
|
||||||
|
step("Assert ENS address displayed") {
|
||||||
|
onSendAddressScreen { resolvedAddress.assertTextContains(ensAddress, substring = true) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert recipient address is displayed") {
|
||||||
|
onSendConfirmScreen { recipientAddress(ensName).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert blockchain address is displayed") {
|
||||||
|
onSendConfirmScreen { blockchainAddress.assertTextContains(ensShortenedAddress, substring = true) }
|
||||||
|
}
|
||||||
|
step("Click on recipient address") {
|
||||||
|
onSendConfirmScreen { recipientAddress(ensName).performClick() }
|
||||||
|
}
|
||||||
|
step("Click on 'Cross' button") {
|
||||||
|
onSendAddressScreen { clearTextFieldButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type invalid ENS name: '$invalidEnsName' in text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(invalidEnsName) }
|
||||||
|
}
|
||||||
|
step("Assert invalid address error title is displayed") {
|
||||||
|
onSendAddressScreen { addressTextFieldTitle.assertTextContains(notAValidAddress) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4574")
|
||||||
|
@DisplayName("Send (address screen): check scan QR code screen")
|
||||||
|
@Test
|
||||||
|
fun checkQrCodeScreenTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val packageName = BuildConfig.APPLICATION_ID
|
||||||
|
val permissionName = Manifest.permission.CAMERA
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalBeforeSection = {
|
||||||
|
device.uiDevice.executeShellCommand("pm grant $packageName $permissionName")
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Click on 'QR' button") {
|
||||||
|
onSendAddressScreen { qrButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Check 'Scan QR code' screen") {
|
||||||
|
checkScanQrScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4576")
|
||||||
|
@DisplayName("Send (address screen): give camera permission")
|
||||||
|
@Test
|
||||||
|
fun giveCameraPermissionScreenTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val sendAmount = "1"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Click on 'QR' button") {
|
||||||
|
onSendAddressScreen { qrButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'While using app' button is displayed") {
|
||||||
|
waitForIdle()
|
||||||
|
PermissionDialogPageObject { allowWhileUsingButton.isDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Allow only this time' button is displayed") {
|
||||||
|
PermissionDialogPageObject { allowOnlyThisTimeButton.isDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Don't allow' button is displayed") {
|
||||||
|
PermissionDialogPageObject { doNotAllowButton.isDisplayed() }
|
||||||
|
}
|
||||||
|
step("CLick on 'While using app' button") {
|
||||||
|
PermissionDialogPageObject { allowWhileUsingButton.click() }
|
||||||
|
}
|
||||||
|
step("Check 'Scan QR code' screen") {
|
||||||
|
checkScanQrScreen()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4578")
|
||||||
|
@DisplayName("Send (address screen): don't give camera permission")
|
||||||
|
@Test
|
||||||
|
fun doNotGiveCameraPermissionScreenTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val sendAmount = "1"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Click on 'QR' button") {
|
||||||
|
onSendAddressScreen { qrButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Don't allow' button") {
|
||||||
|
waitForIdle()
|
||||||
|
PermissionDialogPageObject { doNotAllowButton.click() }
|
||||||
|
}
|
||||||
|
step("Assert 'Camera access denied' bottom sheet title is displayed") {
|
||||||
|
waitForIdle()
|
||||||
|
onCameraAccessDeniedBottomSheet { title.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Camera access denied' bottom sheet subtitle is displayed") {
|
||||||
|
onCameraAccessDeniedBottomSheet { subtitle.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Settings' button is displayed") {
|
||||||
|
onCameraAccessDeniedBottomSheet { settingsButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Select from the gallery' button is displayed") {
|
||||||
|
onCameraAccessDeniedBottomSheet { selectFromGalleryButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Close' button is displayed") {
|
||||||
|
onCameraAccessDeniedBottomSheet { closeButton.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Close' button") {
|
||||||
|
onCameraAccessDeniedBottomSheet { closeButton.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert 'Address' screen is displayed") {
|
||||||
|
onSendAddressScreen { container.assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4590")
|
||||||
|
@DisplayName("Send (address screen): enter 'r' address (XRP)")
|
||||||
|
@Test
|
||||||
|
fun xrpEnterRAddressTest() {
|
||||||
|
val tokenName = "XRP Ledger"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val xrpRecipientAddress = XRP_RECIPIENT_ADDRESS
|
||||||
|
val userTokensScenarioName = "user_tokens_api"
|
||||||
|
val userTokensScenarioState = "XRP"
|
||||||
|
val optionalText = getResourceString(R.string.send_optional_field)
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(userTokensScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(xrpRecipientAddress) }
|
||||||
|
}
|
||||||
|
step("Check 'Destination tag' block") {
|
||||||
|
checkDestinationTagBlock(hint = optionalText)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4591")
|
||||||
|
@DisplayName("Send (address screen): enter 'X' address without tag (XRP)")
|
||||||
|
@Test
|
||||||
|
fun xrpEnterXAddressWithoutTagTest() {
|
||||||
|
val tokenName = "XRP Ledger"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val xrpRecipientAddress = XRP_X_RECIPIENT_ADDRESS
|
||||||
|
val userTokensScenarioName = "user_tokens_api"
|
||||||
|
val userTokensScenarioState = "XRP"
|
||||||
|
val tagAlreadyIncluded = getResourceString(R.string.send_additional_field_already_included)
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(userTokensScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(xrpRecipientAddress) }
|
||||||
|
}
|
||||||
|
step("Check 'Destination tag' block") {
|
||||||
|
checkDestinationTagBlock(hint = tagAlreadyIncluded)
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4592")
|
||||||
|
@DisplayName("Send (address screen): enter 'X' address with tag (XRP)")
|
||||||
|
@Test
|
||||||
|
fun xrpEnterXAddressWithTagTest() {
|
||||||
|
val tokenName = "XRP Ledger"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val xrpRecipientAddress = XRP_X_RECIPIENT_ADDRESS_WITH_TAG
|
||||||
|
val userTokensScenarioName = "user_tokens_api"
|
||||||
|
val userTokensScenarioState = "XRP"
|
||||||
|
val tagAlreadyIncluded = getResourceString(R.string.send_additional_field_already_included)
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(userTokensScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(xrpRecipientAddress) }
|
||||||
|
}
|
||||||
|
step("Check 'Destination tag' block") {
|
||||||
|
checkDestinationTagBlock(hint = tagAlreadyIncluded)
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4594")
|
||||||
|
@DisplayName("Send (address screen): enter own address (XRP)")
|
||||||
|
@Test
|
||||||
|
fun xrpEnterOwnXAddressTest() {
|
||||||
|
val tokenName = "XRP Ledger"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val walletAddress = XRP_X_ADDRESS
|
||||||
|
val userTokensScenarioName = "user_tokens_api"
|
||||||
|
val userTokensScenarioState = "XRP"
|
||||||
|
val sameAsWalletAddress = getResourceString(R.string.send_error_address_same_as_wallet)
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(userTokensScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(walletAddress) }
|
||||||
|
}
|
||||||
|
step("Assert 'Address is the same as wallet address' error title is displayed") {
|
||||||
|
onSendAddressScreen { addressTextFieldTitle.assertTextContains(sameAsWalletAddress) }
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is disabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4593")
|
||||||
|
@DisplayName("Send (address screen): enter tag before address (XRP)")
|
||||||
|
@Test
|
||||||
|
fun xrpEnterTagBeforeAddressTest() {
|
||||||
|
val tokenName = "XRP Ledger"
|
||||||
|
val sendAmount = "1"
|
||||||
|
val xrpRecipientAddress = XRP_X_RECIPIENT_ADDRESS
|
||||||
|
val destinationTag = "12345"
|
||||||
|
val emptyText = ""
|
||||||
|
val userTokensScenarioName = "user_tokens_api"
|
||||||
|
val userTokensScenarioState = "XRP"
|
||||||
|
val tagAlreadyIncluded = getResourceString(R.string.send_additional_field_already_included)
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(userTokensScenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$userTokensScenarioName' to state: '$userTokensScenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Open 'Send Address' screen") {
|
||||||
|
openSendAddressScreen(tokenName, sendAmount)
|
||||||
|
}
|
||||||
|
step("Type destination tag in input text field") {
|
||||||
|
onSendAddressScreen { destinationTagTextField.performTextReplacement(destinationTag) }
|
||||||
|
}
|
||||||
|
step("Type X address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(xrpRecipientAddress) }
|
||||||
|
}
|
||||||
|
step("Destination tag text field doesn't contains destination tag: '$destinationTag'") {
|
||||||
|
onSendAddressScreen { destinationTagTextField.assert (!hasText(destinationTag)) }
|
||||||
|
}
|
||||||
|
step("Destination tag text field cleared") {
|
||||||
|
onSendAddressScreen { destinationTagTextField.assertTextEquals(emptyText) }
|
||||||
|
}
|
||||||
|
step("Check 'Destination tag' block") {
|
||||||
|
checkDestinationTagBlock(hint = tagAlreadyIncluded)
|
||||||
|
}
|
||||||
|
step("Assert 'Next' button is enabled") {
|
||||||
|
onSendAddressScreen { nextButton.assertIsEnabled() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,300 @@
|
||||||
|
package com.tangem.tests.send.confirmScreen
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.POLKADOT_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.*
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.core.ui.R
|
||||||
|
import com.tangem.scenarios.*
|
||||||
|
import com.tangem.screens.*
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class SendConfirmScreenTest : BaseTestCase() {
|
||||||
|
|
||||||
|
@AllureId("4003")
|
||||||
|
@DisplayName("Send (Confirm screen): change sending amount")
|
||||||
|
@Test
|
||||||
|
fun changeSendingAmountTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val inputAmount = "1"
|
||||||
|
val newInputAmount = "0.9"
|
||||||
|
val ethereumAmount = "1.00"
|
||||||
|
val fiatAmount = "$2,535.63"
|
||||||
|
val newFiatAmount = "$2,282.07"
|
||||||
|
val newEthereumAmount = "0.90"
|
||||||
|
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenName'") {
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Send' button") {
|
||||||
|
onTokenDetailsScreen { sendButton().performClick() }
|
||||||
|
}
|
||||||
|
step("Type '$inputAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(inputAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert fiat amount = '$fiatAmount'") {
|
||||||
|
onSendScreen { equivalentInputAmount.assertTextContains(fiatAmount) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type recipient address") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert token amount = '$ethereumAmount'") {
|
||||||
|
onSendConfirmScreen { primaryAmount.assertTextContains(ethereumAmount) }
|
||||||
|
}
|
||||||
|
step("Assert fiat amount = '$fiatAmount'") {
|
||||||
|
onSendConfirmScreen { secondaryAmount.assertTextContains(fiatAmount) }
|
||||||
|
}
|
||||||
|
step("Click on token amount") {
|
||||||
|
onSendConfirmScreen { primaryAmount.performClick() }
|
||||||
|
}
|
||||||
|
step("Clear input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextClearance()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Type '$newInputAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performTextReplacement(newInputAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert fiat amount = '$newFiatAmount'") {
|
||||||
|
onSendScreen { equivalentInputAmount.assertTextContains(newFiatAmount) }
|
||||||
|
}
|
||||||
|
step("Click on 'Continue' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert token amount = '$newEthereumAmount'") {
|
||||||
|
onSendConfirmScreen { primaryAmount.assertTextContains(newEthereumAmount) }
|
||||||
|
}
|
||||||
|
step("Assert fiat amount = '$newFiatAmount'") {
|
||||||
|
onSendConfirmScreen { secondaryAmount.assertTextContains(newFiatAmount) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("552")
|
||||||
|
@DisplayName("Send (Confirm screen): switch to equivalent")
|
||||||
|
@Test
|
||||||
|
fun switchToEquivalentTest() {
|
||||||
|
val tokenName = "Ethereum"
|
||||||
|
val inputAmount = "1"
|
||||||
|
val tokenAmount = "1.00"
|
||||||
|
val ethereumAmount = "ETH 1.00"
|
||||||
|
val fiatAmount = "$2,535.63"
|
||||||
|
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Click on token with name: '$tokenName'") {
|
||||||
|
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Click on 'Send' button") {
|
||||||
|
onTokenDetailsScreen { sendButton().performClick() }
|
||||||
|
}
|
||||||
|
step("Type '$inputAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(inputAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Assert fiat amount = '$fiatAmount'") {
|
||||||
|
onSendScreen { equivalentInputAmount.assertTextContains(fiatAmount) }
|
||||||
|
}
|
||||||
|
step("Click on exchange icon") {
|
||||||
|
onSendScreen { exchangeIcon.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert token amount = '$ethereumAmount'") {
|
||||||
|
onSendScreen { equivalentInputAmount.assertTextContains(ethereumAmount) }
|
||||||
|
}
|
||||||
|
step("Click on exchange icon") {
|
||||||
|
onSendScreen { exchangeIcon.performClick() }
|
||||||
|
}
|
||||||
|
step("Assert fiat amount = '$fiatAmount'") {
|
||||||
|
onSendScreen { equivalentInputAmount.assertTextContains(fiatAmount) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type recipient address") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(recipientAddress) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert primary amount = '$tokenAmount'") {
|
||||||
|
onSendConfirmScreen { primaryAmount.assertTextContains(tokenAmount) }
|
||||||
|
}
|
||||||
|
step("Assert secondary amount = '$fiatAmount'") {
|
||||||
|
onSendConfirmScreen { secondaryAmount.assertTextContains(fiatAmount) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("553")
|
||||||
|
@DisplayName("Send (Confirm screen): check fee for blockchain with unknown fee")
|
||||||
|
@Test
|
||||||
|
fun checkFeeForBlockchainWithUnknownFeeTest() {
|
||||||
|
val tokenName = "Polygon"
|
||||||
|
val inputAmount = "1"
|
||||||
|
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
val currentFeeAmount = "<$0.01"
|
||||||
|
|
||||||
|
setupHooks().run {
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Swipe up to token") {
|
||||||
|
swipeVertical(SwipeDirection.UP)
|
||||||
|
}
|
||||||
|
step("Open 'Send Confirm' screen for token '$tokenName'") {
|
||||||
|
openSendConfirmScreen(
|
||||||
|
tokenName = tokenName,
|
||||||
|
inputAmount = inputAmount,
|
||||||
|
recipientAddress = recipientAddress
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Check network fee block") {
|
||||||
|
checkNetworkFeeBlock(currentFeeAmount = currentFeeAmount, withFeeSelector = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4565")
|
||||||
|
@DisplayName("Send (Confirm screen): check fee for token with unknown fee")
|
||||||
|
@Test
|
||||||
|
fun checkFeeForTokenWithUnknownFeeTest() {
|
||||||
|
val tokenName = "POL (ex-MATIC)"
|
||||||
|
val inputAmount = "1"
|
||||||
|
val recipientAddress = ETHEREUM_RECIPIENT_ADDRESS
|
||||||
|
val currentFeeAmount = "<$0.01"
|
||||||
|
val scenarioName = "eth_estimate_gas"
|
||||||
|
val scenarioState = "UnknownFee"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(scenarioName)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = scenarioName, state = scenarioState)
|
||||||
|
}
|
||||||
|
step("Open 'Main Screen'") {
|
||||||
|
openMainScreen()
|
||||||
|
}
|
||||||
|
step("Synchronize addresses") {
|
||||||
|
synchronizeAddresses()
|
||||||
|
}
|
||||||
|
step("Swipe up to token") {
|
||||||
|
swipeVertical(SwipeDirection.UP)
|
||||||
|
}
|
||||||
|
step("Open 'Send Confirm' screen for token '$tokenName'") {
|
||||||
|
openSendConfirmScreen(
|
||||||
|
tokenName = tokenName,
|
||||||
|
inputAmount = inputAmount,
|
||||||
|
recipientAddress = recipientAddress
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Check network fee block") {
|
||||||
|
checkNetworkFeeBlock(currentFeeAmount = currentFeeAmount, withFeeSelector = true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("554")
|
||||||
|
@DisplayName("Send (Confirm screen): check fee warning")
|
||||||
|
@Test
|
||||||
|
fun checkFeeWarningTest() {
|
||||||
|
val tokenName = "Polkadot"
|
||||||
|
val tokenAmount = "0.1"
|
||||||
|
val warningTitle = getResourceString(R.string.send_fee_unreachable_error_title)
|
||||||
|
val warningMessageResId = R.string.send_fee_unreachable_error_text
|
||||||
|
val currentFeeAmount = "$0.05"
|
||||||
|
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
enableWiFi()
|
||||||
|
enableMobileData()
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$tokenAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(tokenAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(POLKADOT_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Turn off internet") {
|
||||||
|
disableWiFi()
|
||||||
|
disableMobileData()
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Turn on internet") {
|
||||||
|
enableWiFi()
|
||||||
|
enableMobileData()
|
||||||
|
}
|
||||||
|
step("Assert 'Network fee info unreachable' warning title is displayed") {
|
||||||
|
onSendConfirmScreen { warningTitle(warningTitle).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert 'Check your internet connection' warning message is displayed") {
|
||||||
|
onSendConfirmScreen { warningMessage(warningMessageResId).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Assert warning icon is displayed") {
|
||||||
|
onSendConfirmScreen { warningIcon(warningTitle).assertIsDisplayed() }
|
||||||
|
}
|
||||||
|
step("Click on 'Refresh' button") {
|
||||||
|
waitForIdle()
|
||||||
|
onSendConfirmScreen { refreshButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Check network fee block") {
|
||||||
|
checkNetworkFeeBlock(currentFeeAmount = currentFeeAmount, withFeeSelector = false)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.AZERO_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class AzeroWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Aleph Zero"
|
||||||
|
private val amountToLeaveLessThanDeposit = "0.099876587544"
|
||||||
|
private val amountToLeaveGreaterThanDeposit = "0.09"
|
||||||
|
private val depositAmount = "AZERO 0.0000000005"
|
||||||
|
|
||||||
|
private val warningTitle = getResourceString(R.string.send_notification_existential_deposit_title)
|
||||||
|
private val warningMessage = getResourceString(
|
||||||
|
R.string.send_notification_existential_deposit_text, depositAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
@AllureId("4290")
|
||||||
|
@DisplayName("Warnings: check deposit warnings in Azero")
|
||||||
|
@Test
|
||||||
|
fun checkWarning() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveLessThanDeposit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveLessThanDeposit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(AZERO_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Leave $depositAmount' button") {
|
||||||
|
onSendConfirmScreen { leaveDepositButton(depositAmount).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Amount' field") {
|
||||||
|
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveGreaterThanDeposit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanDeposit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Continue' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,162 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.CHIA_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Ignore
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
@Ignore("[REDACTED_JIRA]")
|
||||||
|
class ChiaWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Chia Network"
|
||||||
|
private val mockStateName = "Chia"
|
||||||
|
private val amountToSendUsingLessThanUTXOLimit = "20"
|
||||||
|
private val amountToSendUsingMoreThanUTXOLimit = "51"
|
||||||
|
private val amountToSendUsingExactlyUTXOLimit = "50"
|
||||||
|
private val utxoLimitAmount = "50"
|
||||||
|
|
||||||
|
private val chiaUTXOScenarioName = "chia_get_coin_records"
|
||||||
|
private val moreThanLimitState = "more_than_50_android"
|
||||||
|
|
||||||
|
private val warningTitle = getResourceString(R.string.send_notification_transaction_limit_title)
|
||||||
|
private val warningMessage = getResourceString(
|
||||||
|
R.string.send_notification_transaction_limit_text,
|
||||||
|
tokenName, utxoLimitAmount, utxoLimitAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
@AllureId("4226")
|
||||||
|
@DisplayName("Warnings: check warning, when sending less than 50 utxo")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingLessThanUTXOLimit() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$chiaUTXOScenarioName' to state: '$moreThanLimitState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = chiaUTXOScenarioName, state = moreThanLimitState)
|
||||||
|
}
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToSendUsingLessThanUTXOLimit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToSendUsingLessThanUTXOLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(CHIA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'UTXO limit warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4227")
|
||||||
|
@DisplayName("Warnings: check warning is not displayed, when sending exactly 50 utxo")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingEqualToUTXOLimit() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$chiaUTXOScenarioName' to state: '$moreThanLimitState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = chiaUTXOScenarioName, state = moreThanLimitState)
|
||||||
|
}
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToSendUsingExactlyUTXOLimit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToSendUsingExactlyUTXOLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(CHIA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'UTXO limit warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4228")
|
||||||
|
@DisplayName("Warnings: check warning is not displayed, when sending more than 50 utxo")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingMoreThanUTXOLimit() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$chiaUTXOScenarioName' to state: '$moreThanLimitState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = chiaUTXOScenarioName, state = moreThanLimitState)
|
||||||
|
}
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToSendUsingMoreThanUTXOLimit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToSendUsingMoreThanUTXOLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(CHIA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'UTXO limit warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,183 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.DOGECOIN_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class DogecoinWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Dogecoin"
|
||||||
|
private val amountToLeaveLessThanDust = "5.78654978"
|
||||||
|
private val amountToLeaveMoreThanDust = "5.7"
|
||||||
|
private val amountGreaterThanDust = "0.02"
|
||||||
|
private val amountLessThanDust = "0.005"
|
||||||
|
private val dustAmount = "DOGE 0.01"
|
||||||
|
private val invalidAmountTitle = getResourceString(R.string.send_notification_invalid_amount_title)
|
||||||
|
private val invalidAmountMessage = getResourceString(
|
||||||
|
R.string.send_notification_invalid_minimum_amount_text,
|
||||||
|
dustAmount, dustAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
@AllureId("4213")
|
||||||
|
@DisplayName("Warnings: warning is displayed, if after send balance is less than dust amount (Dogecoin)")
|
||||||
|
@Test
|
||||||
|
fun warningIsDisplayedWhenLeaveLessThanDust() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveLessThanDust' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveLessThanDust)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(DOGECOIN_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid amount warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4214")
|
||||||
|
@DisplayName("Warnings: warning is NOT displayed, if after send balance is more than dust amount (Dogecoin)")
|
||||||
|
@Test
|
||||||
|
fun warningIsNotDisplayedWhenLeaveMoreThanDust() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveMoreThanDust' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveMoreThanDust)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(DOGECOIN_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4211")
|
||||||
|
@DisplayName("Warnings: warning is NOT displayed, when sending more than dust amount (Dogecoin)")
|
||||||
|
@Test
|
||||||
|
fun warningIsNotDisplayedWhenSendingMoreThanDust() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountGreaterThanDust' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountGreaterThanDust)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(DOGECOIN_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4212")
|
||||||
|
@DisplayName("Warnings: warning is displayed, when sending less than dust amount (Dogecoin)")
|
||||||
|
@Test
|
||||||
|
fun warningIsDisplayedWhenSendingLessThanDust() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountLessThanDust' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountLessThanDust)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(DOGECOIN_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid amount warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,160 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.KASPA_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.common.utils.setWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class KaspaWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Kaspa"
|
||||||
|
private val amountToSendUsingLessThanUTXOLimit = "8.0"
|
||||||
|
private val amountToSendUsingMoreThanUTXOLimit = "8.7"
|
||||||
|
private val amountToSendUsingExactlyUTXOLimit = "8.19968"
|
||||||
|
private val utxoLimitAmount = "84"
|
||||||
|
private val availableToSendAmount = "KAS 8.19968"
|
||||||
|
|
||||||
|
private val kaspaUTXOScenarioName = "kaspa_utxo"
|
||||||
|
private val moreThanLimitState = "more_than_84_android"
|
||||||
|
|
||||||
|
private val warningTitle = getResourceString(R.string.send_notification_transaction_limit_title)
|
||||||
|
private val warningMessage = getResourceString(
|
||||||
|
R.string.send_notification_transaction_limit_text,
|
||||||
|
tokenName, utxoLimitAmount, availableToSendAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
@AllureId("4223")
|
||||||
|
@DisplayName("Warnings: check kaspa utxo warning, when sending less than 84 utxo")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingLessThanUTXOLimit() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$kaspaUTXOScenarioName' to state: '$moreThanLimitState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = kaspaUTXOScenarioName, state = moreThanLimitState)
|
||||||
|
}
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToSendUsingLessThanUTXOLimit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToSendUsingLessThanUTXOLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(KASPA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'UTXO limit warning' is NOT displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4225")
|
||||||
|
@DisplayName("Warnings: check kaspa utxo warning, when sending more than 84 utxo")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingMoreThanUTXOLimit() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$kaspaUTXOScenarioName' to state: '$moreThanLimitState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = kaspaUTXOScenarioName, state = moreThanLimitState)
|
||||||
|
}
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToSendUsingMoreThanUTXOLimit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToSendUsingMoreThanUTXOLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(KASPA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'UTXO limit warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4224")
|
||||||
|
@DisplayName("Warnings: check kaspa utxo warning, when sending exactly 84 utxo")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingEqualToUTXOLimit() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Set WireMock scenario: '$kaspaUTXOScenarioName' to state: '$moreThanLimitState'") {
|
||||||
|
setWireMockScenarioState(scenarioName = kaspaUTXOScenarioName, state = moreThanLimitState)
|
||||||
|
}
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToSendUsingExactlyUTXOLimit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToSendUsingExactlyUTXOLimit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(KASPA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'UTXO limit warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.KUSAMA_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class KusamaWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Kusama"
|
||||||
|
private val amountToLeaveLessThanDeposit = "0.300333"
|
||||||
|
private val amountToLeaveGreaterThanDeposit = "0.1"
|
||||||
|
private val depositAmount = "KSM 0.000333333333"
|
||||||
|
|
||||||
|
private val warningTitle = getResourceString(R.string.send_notification_existential_deposit_title)
|
||||||
|
private val warningMessage = getResourceString(
|
||||||
|
R.string.send_notification_existential_deposit_text, depositAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
@AllureId("4291")
|
||||||
|
@DisplayName("Warnings: check deposit warnings in Kusama")
|
||||||
|
@Test
|
||||||
|
fun checkWarning() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveLessThanDeposit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveLessThanDeposit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(KUSAMA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Leave $depositAmount' button") {
|
||||||
|
onSendConfirmScreen { leaveDepositButton(depositAmount).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Amount' field") {
|
||||||
|
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveGreaterThanDeposit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanDeposit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Continue' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.POLKADOT_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class PolkadotWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Polkadot"
|
||||||
|
private val amountToLeaveLessThanDeposit = "1.299"
|
||||||
|
private val amountToLeaveGreaterThanDeposit = "0.2"
|
||||||
|
private val depositAmount = "DOT 1.00"
|
||||||
|
|
||||||
|
private val warningTitle = getResourceString(R.string.send_notification_existential_deposit_title)
|
||||||
|
private val warningMessage = getResourceString(
|
||||||
|
R.string.send_notification_existential_deposit_text, depositAmount
|
||||||
|
)
|
||||||
|
|
||||||
|
@AllureId("4289")
|
||||||
|
@DisplayName("Warnings: check deposit warnings in Polkadot")
|
||||||
|
@Test
|
||||||
|
fun checkWarning() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveLessThanDeposit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveLessThanDeposit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(POLKADOT_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Leave $depositAmount' button") {
|
||||||
|
onSendConfirmScreen { leaveDepositButton(depositAmount).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Amount' field") {
|
||||||
|
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveGreaterThanDeposit' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanDeposit)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Continue' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Existential deposit warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,180 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.SOLANA_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class SolanaWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Solana"
|
||||||
|
private val amountToLeaveLessThanRent = "0.0016941"
|
||||||
|
private val amountToLeaveGreaterThanRent = "0.0000941"
|
||||||
|
private val amountToLeaveRentOnly = "0.00168934"
|
||||||
|
private val rentAmount = "SOL 0.00089088"
|
||||||
|
|
||||||
|
private val invalidAmountTitle = getResourceString(R.string.send_notification_invalid_amount_title)
|
||||||
|
private val invalidAmountMessage = getResourceString(R.string.send_notification_invalid_amount_rent_fee, rentAmount)
|
||||||
|
|
||||||
|
@AllureId("564")
|
||||||
|
@DisplayName("Warnings: warning is displayed, if after send balance is less than rent amount (SOLANA)")
|
||||||
|
@Test
|
||||||
|
fun warningIsDisplayedWhenLeaveLessThanRent() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveLessThanRent' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveLessThanRent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid amount warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("567")
|
||||||
|
@DisplayName("Warnings: warning is not displayed, if after send balance is greater than rent amount (SOLANA)")
|
||||||
|
@Test
|
||||||
|
fun warningIsNotDisplayedWhenLeaveGreaterThanRent() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveGreaterThanRent' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveGreaterThanRent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("566")
|
||||||
|
@DisplayName("Warnings: warning is not displayed, if after send balance is equal to rent amount (SOLANA)")
|
||||||
|
@Test
|
||||||
|
fun warningIsNotDisplayedWhenLeaveOnlyRent() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$amountToLeaveRentOnly' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(amountToLeaveRentOnly)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("565")
|
||||||
|
@DisplayName("Warnings: warning is not displayed, if after send balance is zero (SOLANA)")
|
||||||
|
@Test
|
||||||
|
fun warningIsNotDisplayedWhenLeaveZeroSol() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type max amount in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
maxButton.performClick()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(SOLANA_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = invalidAmountTitle,
|
||||||
|
message = invalidAmountMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.XLM_ACTIVATED_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.XLM_NON_ACTIVATED_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class StellarWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Stellar"
|
||||||
|
private val mockStateName = "XLM"
|
||||||
|
private val lessThanReserveAmount = "0.5"
|
||||||
|
private val equalToReserveAmount = "1"
|
||||||
|
private val reserveAmount = "XLM 1.00"
|
||||||
|
private val greaterThanReserveAmount = "2"
|
||||||
|
|
||||||
|
private val warningTitle =
|
||||||
|
getResourceString(R.string.send_notification_invalid_reserve_amount_title, reserveAmount)
|
||||||
|
private val warningMessage = getResourceString(R.string.send_notification_invalid_reserve_amount_text)
|
||||||
|
|
||||||
|
@AllureId("4287")
|
||||||
|
@DisplayName("Warnings: check warning, when sending less than reserve")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingLessThanReserve() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$lessThanReserveAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(lessThanReserveAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type non activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Address' field") {
|
||||||
|
onSendConfirmScreen { recipientAddress(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type an activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XLM_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4286")
|
||||||
|
@DisplayName("Warnings: check warning when sending amount equal to reserve")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingAmountEqualToReserve() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$equalToReserveAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(equalToReserveAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type non activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Address' field") {
|
||||||
|
onSendConfirmScreen { recipientAddress(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type an activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XLM_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4288")
|
||||||
|
@DisplayName("Warnings: check warning when sending greater than reserve")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingGreaterThanReserve() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$greaterThanReserveAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(greaterThanReserveAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type non activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Address' field") {
|
||||||
|
onSendConfirmScreen { recipientAddress(XLM_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type an activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XLM_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,120 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.TEZOS_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.extensions.SwipeDirection
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.extensions.swipeVertical
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class TezosWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "Tezos"
|
||||||
|
private val reduceAmount = "0.000001"
|
||||||
|
private val sendAmount = "0.01"
|
||||||
|
|
||||||
|
private val feeIsHighTitle = getResourceString(R.string.send_notification_high_fee_title)
|
||||||
|
private val feeIsHighMessage =
|
||||||
|
getResourceString(R.string.send_notification_high_fee_text, tokenName, reduceAmount)
|
||||||
|
|
||||||
|
@AllureId("4229")
|
||||||
|
@DisplayName("Warnings: warning is displayed when sending max amount")
|
||||||
|
@Test
|
||||||
|
fun warningIsDisplayedWhenSendMaxAmount() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Click 'Max amount' button") {
|
||||||
|
onSendScreen {
|
||||||
|
maxButton.clickWithAssertion()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(TEZOS_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Swipe up to see the warning") {
|
||||||
|
swipeVertical(SwipeDirection.UP)
|
||||||
|
}
|
||||||
|
step("Assert 'Fee is high warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = feeIsHighTitle,
|
||||||
|
message = feeIsHighMessage,
|
||||||
|
sendButtonIsDisabled = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Reduce' button") {
|
||||||
|
onSendConfirmScreen { reduceAmountButton(reduceAmount).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Fee is high warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = feeIsHighTitle,
|
||||||
|
message = feeIsHighMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4230")
|
||||||
|
@DisplayName("Warnings: warning is not displayed when sending not max amount")
|
||||||
|
@Test
|
||||||
|
fun warningIsNotDisplayedWhenSendNotMaxAmount() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName)
|
||||||
|
}
|
||||||
|
step("Type '$sendAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(sendAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(TEZOS_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Fee is high warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = feeIsHighTitle,
|
||||||
|
message = feeIsHighMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,195 @@
|
||||||
|
package com.tangem.tests.send.warnings
|
||||||
|
|
||||||
|
import com.tangem.common.BaseTestCase
|
||||||
|
import com.tangem.common.constants.TestConstants.QUOTES_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.USER_TOKENS_API_SCENARIO
|
||||||
|
import com.tangem.common.constants.TestConstants.XRP_ACTIVATED_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.constants.TestConstants.XRP_NON_ACTIVATED_RECIPIENT_ADDRESS
|
||||||
|
import com.tangem.common.extensions.clickWithAssertion
|
||||||
|
import com.tangem.common.utils.resetWireMockScenarioState
|
||||||
|
import com.tangem.scenarios.checkSendWarning
|
||||||
|
import com.tangem.scenarios.openSendScreen
|
||||||
|
import com.tangem.screens.onSendAddressScreen
|
||||||
|
import com.tangem.screens.onSendConfirmScreen
|
||||||
|
import com.tangem.screens.onSendScreen
|
||||||
|
import com.tangem.wallet.R
|
||||||
|
import dagger.hilt.android.testing.HiltAndroidTest
|
||||||
|
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||||
|
import io.qameta.allure.kotlin.AllureId
|
||||||
|
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||||
|
import org.junit.Test
|
||||||
|
|
||||||
|
@HiltAndroidTest
|
||||||
|
class XRPWarningsTest : BaseTestCase() {
|
||||||
|
private val tokenName = "XRP Ledger"
|
||||||
|
private val mockStateName = "XRP"
|
||||||
|
private val lessThanReserveAmount = "0.5"
|
||||||
|
private val equalToReserveAmount = "1"
|
||||||
|
private val reserveAmount = "XRP 1.00"
|
||||||
|
private val greaterThanReserveAmount = "2"
|
||||||
|
|
||||||
|
private val warningTitle =
|
||||||
|
getResourceString(R.string.send_notification_invalid_reserve_amount_title, reserveAmount)
|
||||||
|
private val warningMessage = getResourceString(R.string.send_notification_invalid_reserve_amount_text)
|
||||||
|
|
||||||
|
@AllureId("4255")
|
||||||
|
@DisplayName("Warnings: check warning, when sending less than reserve")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingLessThanReserve() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$lessThanReserveAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(lessThanReserveAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type non activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Address' field") {
|
||||||
|
onSendConfirmScreen { recipientAddress(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type an activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XRP_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4285")
|
||||||
|
@DisplayName("Warnings: check warning when sending amount equal to reserve")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingAmountEqualToReserve() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$equalToReserveAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(equalToReserveAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type non activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Address' field") {
|
||||||
|
onSendConfirmScreen { recipientAddress(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type an activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XRP_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@AllureId("4284")
|
||||||
|
@DisplayName("Warnings: check warning when sending greater than reserve")
|
||||||
|
@Test
|
||||||
|
fun checkWarningWhenSendingGreaterThanReserve() {
|
||||||
|
setupHooks(
|
||||||
|
additionalAfterSection = {
|
||||||
|
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||||
|
resetWireMockScenarioState(QUOTES_API_SCENARIO)
|
||||||
|
}
|
||||||
|
).run {
|
||||||
|
step("Open 'Send Screen' with token: $tokenName") {
|
||||||
|
openSendScreen(tokenName, mockStateName)
|
||||||
|
}
|
||||||
|
step("Type '$greaterThanReserveAmount' in input text field") {
|
||||||
|
onSendScreen {
|
||||||
|
amountInputTextField.performClick()
|
||||||
|
amountInputTextField.performTextReplacement(greaterThanReserveAmount)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type non activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
step("Click on 'Address' field") {
|
||||||
|
onSendConfirmScreen { recipientAddress(XRP_NON_ACTIVATED_RECIPIENT_ADDRESS).clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Type an activated address in input text field") {
|
||||||
|
onSendAddressScreen { addressTextField.performTextReplacement(XRP_ACTIVATED_RECIPIENT_ADDRESS) }
|
||||||
|
}
|
||||||
|
step("Click on 'Next' button") {
|
||||||
|
onSendScreen { continueButton.clickWithAssertion() }
|
||||||
|
}
|
||||||
|
step("Assert 'Invalid reserve amount warning' is not displayed") {
|
||||||
|
checkSendWarning(
|
||||||
|
title = warningTitle,
|
||||||
|
message = warningMessage,
|
||||||
|
isDisplayed = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -310,4 +310,8 @@
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
</service>
|
</service>
|
||||||
</application>
|
</application>
|
||||||
|
<queries>
|
||||||
|
<!-- Needed from Android 11 to open Google Wallet for payment with Visa card -->
|
||||||
|
<package android:name="com.google.android.apps.walletnfcrel" />
|
||||||
|
</queries>
|
||||||
</manifest>
|
</manifest>
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,9 @@ import com.tangem.TangemSdkLogger
|
||||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
|
import com.tangem.core.abtests.manager.ABTestsManager
|
||||||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||||
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||||
|
|
@ -151,4 +153,8 @@ interface ApplicationEntryPoint {
|
||||||
fun getHotWalletFeatureToggles(): HotWalletFeatureToggles
|
fun getHotWalletFeatureToggles(): HotWalletFeatureToggles
|
||||||
|
|
||||||
fun getWcInitializeUseCase(): WcInitializeUseCase
|
fun getWcInitializeUseCase(): WcInitializeUseCase
|
||||||
|
|
||||||
|
fun getTrackingContextProxy(): TrackingContextProxy
|
||||||
|
|
||||||
|
fun getABTestsManager(): ABTestsManager
|
||||||
}
|
}
|
||||||
|
|
@ -302,7 +302,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createAppThemeModeFlow(): SharedFlow<AppThemeMode> {
|
private fun createAppThemeModeFlow(): SharedFlow<AppThemeMode> {
|
||||||
val tangemApplication = application as TangemApplication
|
val tangemApplication = requireNotNull(application as? TangemApplication) {
|
||||||
|
"Application is null"
|
||||||
|
}
|
||||||
|
|
||||||
return tangemApplication.getAppThemeModeUseCase()
|
return tangemApplication.getAppThemeModeUseCase()
|
||||||
.filterNotNull()
|
.filterNotNull()
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@ import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
||||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||||
import com.tangem.common.routing.AppRouter
|
import com.tangem.common.routing.AppRouter
|
||||||
|
import com.tangem.core.abtests.manager.ABTestsManager
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.core.analytics.api.ParamsInterceptor
|
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||||
|
|
@ -240,6 +241,12 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
||||||
private val wcInitializeUseCase
|
private val wcInitializeUseCase
|
||||||
get() = entryPoint.getWcInitializeUseCase()
|
get() = entryPoint.getWcInitializeUseCase()
|
||||||
|
|
||||||
|
private val trackingContextProxy
|
||||||
|
get() = entryPoint.getTrackingContextProxy()
|
||||||
|
|
||||||
|
private val abTestsManager: ABTestsManager
|
||||||
|
get() = entryPoint.getABTestsManager()
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
private val appScope = MainScope()
|
private val appScope = MainScope()
|
||||||
|
|
@ -310,6 +317,8 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
||||||
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abTestsManager.init()
|
||||||
|
|
||||||
appScope.launch {
|
appScope.launch {
|
||||||
launch(Dispatchers.IO) {
|
launch(Dispatchers.IO) {
|
||||||
loadNativeLibraries()
|
loadNativeLibraries()
|
||||||
|
|
@ -385,6 +394,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
||||||
userWalletsListRepository = userWalletsListRepository,
|
userWalletsListRepository = userWalletsListRepository,
|
||||||
tangemHotSdk = tangemHotSdk,
|
tangemHotSdk = tangemHotSdk,
|
||||||
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
hotWalletFeatureToggles = hotWalletFeatureToggles,
|
||||||
|
trackingContextProxy = trackingContextProxy,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
package com.tangem.tap.common.analytics
|
|
||||||
|
|
||||||
import com.tangem.core.analytics.Analytics
|
|
||||||
import com.tangem.core.analytics.utils.AnalyticsContextProxy
|
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
|
||||||
import com.tangem.tap.common.extensions.addContext
|
|
||||||
import com.tangem.tap.common.extensions.eraseContext
|
|
||||||
import com.tangem.tap.common.extensions.removeContext
|
|
||||||
import com.tangem.tap.common.extensions.setContext
|
|
||||||
|
|
||||||
/**
|
|
||||||
[REDACTED_AUTHOR]
|
|
||||||
*/
|
|
||||||
internal class DefaultAnalyticsContextProxy : AnalyticsContextProxy {
|
|
||||||
|
|
||||||
override fun setContext(scanResponse: ScanResponse) {
|
|
||||||
Analytics.setContext(scanResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun eraseContext() {
|
|
||||||
Analytics.eraseContext()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun addContext(scanResponse: ScanResponse) {
|
|
||||||
Analytics.addContext(scanResponse)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun removeContext() {
|
|
||||||
Analytics.removeContext()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package com.tangem.tap.common.analytics
|
|
||||||
|
|
||||||
import com.tangem.core.analytics.Analytics
|
|
||||||
import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase
|
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
|
||||||
import com.tangem.tap.common.extensions.setContext
|
|
||||||
|
|
||||||
internal class DefaultChangeCardAnalyticsContextUseCase : ChangeCardAnalyticsContextUseCase {
|
|
||||||
|
|
||||||
override fun invoke(scanResponse: ScanResponse) {
|
|
||||||
Analytics.setContext(scanResponse)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -0,0 +1,98 @@
|
||||||
|
package com.tangem.tap.common.analytics
|
||||||
|
|
||||||
|
import com.tangem.core.abtests.manager.ABTestsManager
|
||||||
|
import com.tangem.core.analytics.Analytics
|
||||||
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||||
|
import com.tangem.tap.common.extensions.addContext
|
||||||
|
import com.tangem.tap.common.extensions.addHotWalletContext
|
||||||
|
import com.tangem.tap.common.extensions.eraseContext
|
||||||
|
import com.tangem.tap.common.extensions.removeContext
|
||||||
|
import com.tangem.tap.common.extensions.setContext
|
||||||
|
import com.tangem.tap.common.extensions.setHotWalletContext
|
||||||
|
import com.tangem.common.extensions.calculateSha256
|
||||||
|
import com.tangem.common.extensions.toHexString
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
|
|
||||||
|
/**
|
||||||
|
[REDACTED_AUTHOR]
|
||||||
|
*/
|
||||||
|
internal class DefaultTrackingContextProxy(private val abTestsManager: ABTestsManager) : TrackingContextProxy {
|
||||||
|
|
||||||
|
override fun setContext(scanResponse: ScanResponse) {
|
||||||
|
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||||
|
|
||||||
|
Analytics.setContext(userWalletId, scanResponse)
|
||||||
|
|
||||||
|
abTestsManager.setUserProperties(
|
||||||
|
userId = calculateUserIdHash(userWalletId),
|
||||||
|
batch = scanResponse.card.batchId,
|
||||||
|
productType = scanResponse.productType.name,
|
||||||
|
firmware = scanResponse.card.firmwareVersion.stringValue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setContext(userWallet: UserWallet) {
|
||||||
|
Analytics.setContext(userWallet)
|
||||||
|
|
||||||
|
when (userWallet) {
|
||||||
|
is UserWallet.Cold -> {
|
||||||
|
setColdWalletUserProperties(userWallet)
|
||||||
|
}
|
||||||
|
is UserWallet.Hot -> {
|
||||||
|
setHotWalletUserProperties(userWallet)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addContext(userWallet: UserWallet) {
|
||||||
|
Analytics.addContext(userWallet)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setHotWalletContext() {
|
||||||
|
Analytics.setHotWalletContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun eraseContext() {
|
||||||
|
Analytics.eraseContext()
|
||||||
|
abTestsManager.removeUserProperties()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addContext(scanResponse: ScanResponse) {
|
||||||
|
Analytics.addContext(scanResponse)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun addHotWalletContext() {
|
||||||
|
Analytics.addHotWalletContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeContext() {
|
||||||
|
Analytics.removeContext()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateUserIdHash(userWalletId: UserWalletId?): String? {
|
||||||
|
return userWalletId?.value
|
||||||
|
?.calculateSha256()
|
||||||
|
?.toHexString()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setColdWalletUserProperties(userWallet: UserWallet.Cold) {
|
||||||
|
abTestsManager.setUserProperties(
|
||||||
|
userId = calculateUserIdHash(userWallet.walletId),
|
||||||
|
batch = userWallet.scanResponse.card.batchId,
|
||||||
|
productType = userWallet.scanResponse.productType.name,
|
||||||
|
firmware = userWallet.scanResponse.card.firmwareVersion.stringValue,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun setHotWalletUserProperties(userWallet: UserWallet.Hot) {
|
||||||
|
abTestsManager.setUserProperties(
|
||||||
|
userId = calculateUserIdHash(userWallet.walletId),
|
||||||
|
batch = null,
|
||||||
|
productType = "Mobile Wallet",
|
||||||
|
firmware = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,6 +6,7 @@ class BlockchainApiExceptionEvent(
|
||||||
selectedHost: String,
|
selectedHost: String,
|
||||||
exceptionHost: String,
|
exceptionHost: String,
|
||||||
error: String,
|
error: String,
|
||||||
|
blockchain: String,
|
||||||
) : AnalyticsEvent(
|
) : AnalyticsEvent(
|
||||||
category = "BlockchainSdk",
|
category = "BlockchainSdk",
|
||||||
event = "Exception",
|
event = "Exception",
|
||||||
|
|
@ -13,5 +14,6 @@ class BlockchainApiExceptionEvent(
|
||||||
AnalyticsParam.BLOCKCHAIN_SELECTED_HOST to selectedHost,
|
AnalyticsParam.BLOCKCHAIN_SELECTED_HOST to selectedHost,
|
||||||
AnalyticsParam.BLOCKCHAIN_EXCEPTION_HOST to exceptionHost,
|
AnalyticsParam.BLOCKCHAIN_EXCEPTION_HOST to exceptionHost,
|
||||||
AnalyticsParam.ERROR_DESCRIPTION to error,
|
AnalyticsParam.ERROR_DESCRIPTION to error,
|
||||||
|
AnalyticsParam.BLOCKCHAIN to blockchain,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
@ -1,6 +1,8 @@
|
||||||
package com.tangem.tap.common.analytics.handlers
|
package com.tangem.tap.common.analytics.handlers
|
||||||
|
|
||||||
|
import com.tangem.blockchain.common.Blockchain
|
||||||
import com.tangem.blockchain.common.ExceptionHandlerOutput
|
import com.tangem.blockchain.common.ExceptionHandlerOutput
|
||||||
|
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||||
import com.tangem.tap.common.analytics.events.BlockchainApiExceptionEvent
|
import com.tangem.tap.common.analytics.events.BlockchainApiExceptionEvent
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
@ -8,12 +10,13 @@ import javax.inject.Inject
|
||||||
class BlockchainExceptionHandler @Inject constructor(
|
class BlockchainExceptionHandler @Inject constructor(
|
||||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||||
) : ExceptionHandlerOutput {
|
) : ExceptionHandlerOutput {
|
||||||
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String) {
|
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String, blockchain: Blockchain) {
|
||||||
analyticsErrorHandler.sendErrorEvent(
|
analyticsErrorHandler.sendErrorEvent(
|
||||||
BlockchainApiExceptionEvent(
|
BlockchainApiExceptionEvent(
|
||||||
selectedHost = nextHost,
|
selectedHost = nextHost,
|
||||||
exceptionHost = currentHost,
|
exceptionHost = currentHost,
|
||||||
error = message,
|
error = message,
|
||||||
|
blockchain = blockchain.toNetworkId(),
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,22 @@
|
||||||
|
package com.tangem.tap.common.analytics.paramsInterceptor
|
||||||
|
|
||||||
|
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
|
|
||||||
|
class HotWalletContextInterceptor(
|
||||||
|
val parent: ParamsInterceptor? = null,
|
||||||
|
) : ParamsInterceptor {
|
||||||
|
|
||||||
|
override fun id(): String = HotWalletContextInterceptor.id()
|
||||||
|
|
||||||
|
override fun canBeAppliedTo(event: AnalyticsEvent): Boolean = true
|
||||||
|
|
||||||
|
override fun intercept(params: MutableMap<String, String>) {
|
||||||
|
params[AnalyticsParam.PRODUCT_TYPE] = "Mobile Wallet"
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
fun id(): String = HotWalletContextInterceptor::class.java.simpleName
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -9,7 +9,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
||||||
*/
|
*/
|
||||||
class LinkedCardContextInterceptor(
|
class LinkedCardContextInterceptor(
|
||||||
scanResponse: ScanResponse,
|
scanResponse: ScanResponse,
|
||||||
val parent: LinkedCardContextInterceptor? = null,
|
val parent: ParamsInterceptor? = null,
|
||||||
) : ParamsInterceptor {
|
) : ParamsInterceptor {
|
||||||
|
|
||||||
private val contextInterceptor = CardContextInterceptor(scanResponse)
|
private val contextInterceptor = CardContextInterceptor(scanResponse)
|
||||||
|
|
|
||||||
|
|
@ -3,7 +3,9 @@ package com.tangem.tap.common.extensions
|
||||||
import com.tangem.core.analytics.Analytics
|
import com.tangem.core.analytics.Analytics
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||||
|
import com.tangem.tap.common.analytics.paramsInterceptor.HotWalletContextInterceptor
|
||||||
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
|
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -13,8 +15,7 @@ import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterc
|
||||||
/**
|
/**
|
||||||
* Sets the new context
|
* Sets the new context
|
||||||
*/
|
*/
|
||||||
fun Analytics.setContext(scanResponse: ScanResponse) {
|
fun Analytics.setContext(userWalletId: UserWalletId?, scanResponse: ScanResponse) {
|
||||||
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
|
||||||
if (userWalletId != null) {
|
if (userWalletId != null) {
|
||||||
setUserId(userWalletId.stringValue)
|
setUserId(userWalletId.stringValue)
|
||||||
}
|
}
|
||||||
|
|
@ -24,19 +25,46 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
|
||||||
|
|
||||||
fun Analytics.setContext(userWallet: UserWallet) {
|
fun Analytics.setContext(userWallet: UserWallet) {
|
||||||
setUserId(userWallet.walletId.stringValue)
|
setUserId(userWallet.walletId.stringValue)
|
||||||
// TODO add product type for hot ([REDACTED_TASK_KEY] [Hot Wallet] Analytics)
|
|
||||||
|
|
||||||
if (userWallet is UserWallet.Cold) {
|
when (userWallet) {
|
||||||
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
|
is UserWallet.Cold -> {
|
||||||
|
removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||||
|
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
|
||||||
|
}
|
||||||
|
is UserWallet.Hot -> {
|
||||||
|
removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||||
|
addParamsInterceptor(HotWalletContextInterceptor())
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Analytics.setHotWalletContext() {
|
||||||
|
addParamsInterceptor(HotWalletContextInterceptor())
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Erases the context
|
* Erases the context
|
||||||
*/
|
*/
|
||||||
fun Analytics.eraseContext() {
|
fun Analytics.eraseContext() {
|
||||||
clearUserId()
|
clearUserId()
|
||||||
removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||||
|
removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a new context and keeps a previous context as the parent of the new one
|
||||||
|
*/
|
||||||
|
fun Analytics.addContext(userWallet: UserWallet) {
|
||||||
|
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||||
|
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||||
|
|
||||||
|
val newContext = when (userWallet) {
|
||||||
|
is UserWallet.Cold -> LinkedCardContextInterceptor(userWallet.scanResponse, parent = currentContext)
|
||||||
|
is UserWallet.Hot -> HotWalletContextInterceptor(parent = currentContext)
|
||||||
|
}
|
||||||
|
|
||||||
|
setUserId(userId = userWallet.walletId.stringValue)
|
||||||
|
addParamsInterceptor(newContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -48,18 +76,32 @@ fun Analytics.addContext(scanResponse: ScanResponse) {
|
||||||
setUserId(userWalletId.stringValue)
|
setUserId(userWalletId.stringValue)
|
||||||
}
|
}
|
||||||
|
|
||||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||||
|
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||||
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
|
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
|
||||||
|
|
||||||
addParamsInterceptor(newContext)
|
addParamsInterceptor(newContext)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Analytics.addHotWalletContext() {
|
||||||
|
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
||||||
|
val newContext = HotWalletContextInterceptor(currentContext)
|
||||||
|
|
||||||
|
addParamsInterceptor(newContext)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the current context and restores the previous one if it was present.
|
* Removes the current context and restores the previous one if it was present.
|
||||||
*/
|
*/
|
||||||
fun Analytics.removeContext() {
|
fun Analytics.removeContext() {
|
||||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||||
val previousContext = currentContext?.parent ?: return
|
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||||
|
|
||||||
|
val previousContext = when (currentContext) {
|
||||||
|
is LinkedCardContextInterceptor -> currentContext.parent
|
||||||
|
is HotWalletContextInterceptor -> currentContext.parent
|
||||||
|
else -> null
|
||||||
|
} ?: return
|
||||||
|
|
||||||
addParamsInterceptor(previousContext)
|
addParamsInterceptor(previousContext)
|
||||||
}
|
}
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
package com.tangem.tap.common.extensions
|
|
||||||
|
|
||||||
import java.math.BigDecimal
|
|
||||||
import java.math.RoundingMode
|
|
||||||
import java.text.DecimalFormat
|
|
||||||
import java.text.DecimalFormatSymbols
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
// TODO: move extensions to utils
|
|
||||||
fun BigDecimal.toFormattedString(
|
|
||||||
decimals: Int,
|
|
||||||
roundingMode: RoundingMode = RoundingMode.DOWN,
|
|
||||||
locale: Locale = Locale.US,
|
|
||||||
): String {
|
|
||||||
val symbols = DecimalFormatSymbols(locale)
|
|
||||||
val df = DecimalFormat()
|
|
||||||
df.decimalFormatSymbols = symbols
|
|
||||||
df.maximumFractionDigits = decimals
|
|
||||||
df.minimumFractionDigits = 0
|
|
||||||
df.isGroupingUsed = true
|
|
||||||
df.roundingMode = roundingMode
|
|
||||||
return df.format(this)
|
|
||||||
}
|
|
||||||
|
|
@ -54,8 +54,8 @@ private class CoilTimberLogger : Logger {
|
||||||
|
|
||||||
override fun log(tag: String, priority: Int, message: String?, throwable: Throwable?) {
|
override fun log(tag: String, priority: Int, message: String?, throwable: Throwable?) {
|
||||||
with(Timber.tag(COIL_LOG_TAG)) {
|
with(Timber.tag(COIL_LOG_TAG)) {
|
||||||
throwable?.let { e -> e(e, message) }
|
if (throwable != null) e(throwable, message)
|
||||||
message?.let { msg -> d(msg) }
|
if (message != null) d(message)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -54,8 +54,8 @@ class PushNotificationDelegate(private val context: Context) {
|
||||||
.setContentIntent(pendingIntent)
|
.setContentIntent(pendingIntent)
|
||||||
.setVibrate(vibratePattern)
|
.setVibrate(vibratePattern)
|
||||||
.apply {
|
.apply {
|
||||||
imageUrl?.let { uri ->
|
if (imageUrl != null) {
|
||||||
val bitmap = getBitmapImageFromUrl(uri)
|
val bitmap = getBitmapImageFromUrl(imageUrl)
|
||||||
setStyle(
|
setStyle(
|
||||||
NotificationCompat
|
NotificationCompat
|
||||||
.BigPictureStyle()
|
.BigPictureStyle()
|
||||||
|
|
@ -64,7 +64,10 @@ class PushNotificationDelegate(private val context: Context) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
val notificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
val service = context.getSystemService(Context.NOTIFICATION_SERVICE)
|
||||||
|
val notificationManager = requireNotNull(service as? NotificationManager) {
|
||||||
|
"NotificationManager not available"
|
||||||
|
}
|
||||||
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
val notificationChannel = NotificationChannel(
|
val notificationChannel = NotificationChannel(
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,7 @@ import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||||
import org.rekotlin.Action
|
import org.rekotlin.Action
|
||||||
|
|
||||||
|
@Suppress("CanBeNonNullable")
|
||||||
fun appReducer(action: Action, state: AppState?): AppState {
|
fun appReducer(action: Action, state: AppState?): AppState {
|
||||||
requireNotNull(state)
|
requireNotNull(state)
|
||||||
if (action is AppAction.RestoreState) return action.state
|
if (action is AppAction.RestoreState) return action.state
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.flow.*
|
import kotlinx.coroutines.flow.*
|
||||||
import org.rekotlin.Middleware
|
import org.rekotlin.Middleware
|
||||||
|
|
||||||
|
@Suppress("MemberNameEqualsClassName")
|
||||||
internal object LegacyMiddleware {
|
internal object LegacyMiddleware {
|
||||||
private val prepareDetailsScreenJobHolder = JobHolder()
|
private val prepareDetailsScreenJobHolder = JobHolder()
|
||||||
|
|
||||||
|
|
@ -29,7 +30,14 @@ internal object LegacyMiddleware {
|
||||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||||
|
|
||||||
selectedUserWallet()
|
selectedUserWallet()
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged { old, new ->
|
||||||
|
if (old is UserWallet.Cold && new is UserWallet.Cold) {
|
||||||
|
old.walletId == new.walletId &&
|
||||||
|
old.scanResponse == new.scanResponse
|
||||||
|
} else {
|
||||||
|
old.walletId == new.walletId
|
||||||
|
}
|
||||||
|
}
|
||||||
.onEach { selectedUserWallet ->
|
.onEach { selectedUserWallet ->
|
||||||
val initializedAppSettingsStateContent = initializeAppSettingsState(
|
val initializedAppSettingsStateContent = initializeAppSettingsState(
|
||||||
shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||||
|
|
@ -78,6 +86,7 @@ internal object LegacyMiddleware {
|
||||||
isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository)
|
isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository)
|
||||||
.getBalanceHidingSettings().isHidingEnabledInSettings,
|
.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||||
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
|
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
|
||||||
|
hasSecuredWallets = store.inject(DaggerGraphState::userWalletsListRepository).hasSecuredWallets(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -41,8 +41,8 @@ object SimpleCancelableAlertDialog {
|
||||||
context: Context,
|
context: Context,
|
||||||
): AlertDialog {
|
): AlertDialog {
|
||||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||||
setTitle(titleRes?.let { context.getString(it) } ?: title)
|
setTitle(if (titleRes != null) context.getString(titleRes) else title)
|
||||||
setMessage(messageRes?.let { context.getString(it) } ?: message)
|
setMessage(if (messageRes != null) context.getString(messageRes) else message)
|
||||||
setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
|
setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
|
||||||
if (secondaryButtonRes != null) {
|
if (secondaryButtonRes != null) {
|
||||||
setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction() }
|
setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction() }
|
||||||
|
|
|
||||||
|
|
@ -2,8 +2,13 @@ package com.tangem.tap.data
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.squareup.moshi.Moshi
|
import com.squareup.moshi.Moshi
|
||||||
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
|
import com.tangem.datasource.di.NetworkMoshi
|
||||||
|
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||||
|
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||||
|
import com.tangem.datasource.local.preferences.utils.getSyncOrNull
|
||||||
|
import com.tangem.datasource.local.preferences.utils.store
|
||||||
import com.tangem.datasource.local.visa.TangemPayStorage
|
import com.tangem.datasource.local.visa.TangemPayStorage
|
||||||
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.visa.model.VisaAuthTokens
|
import com.tangem.domain.visa.model.VisaAuthTokens
|
||||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -11,7 +16,6 @@ import dagger.hilt.android.qualifiers.ApplicationContext
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
import javax.inject.Singleton
|
import javax.inject.Singleton
|
||||||
import kotlin.text.encodeToByteArray
|
|
||||||
|
|
||||||
private const val DEFAULT_KEY = "tangem_pay_default_key"
|
private const val DEFAULT_KEY = "tangem_pay_default_key"
|
||||||
private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
||||||
|
|
@ -19,7 +23,9 @@ private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
||||||
@Singleton
|
@Singleton
|
||||||
internal class DefaultTangemPayStorage @Inject constructor(
|
internal class DefaultTangemPayStorage @Inject constructor(
|
||||||
@ApplicationContext applicationContext: Context,
|
@ApplicationContext applicationContext: Context,
|
||||||
|
@NetworkMoshi moshi: Moshi,
|
||||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||||
|
private val appPreferencesStore: AppPreferencesStore,
|
||||||
) : TangemPayStorage {
|
) : TangemPayStorage {
|
||||||
|
|
||||||
private val secureStorage by lazy {
|
private val secureStorage by lazy {
|
||||||
|
|
@ -29,14 +35,21 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
||||||
name = "tangem_pay_storage",
|
name = "tangem_pay_storage",
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
private val moshi by lazy {
|
|
||||||
Moshi.Builder()
|
|
||||||
.add(KotlinJsonAdapterFactory())
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private val tokensAdapter by lazy { moshi.adapter(VisaAuthTokens::class.java) }
|
private val tokensAdapter by lazy { moshi.adapter(VisaAuthTokens::class.java) }
|
||||||
|
|
||||||
|
override suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) {
|
||||||
|
withContext(dispatcherProvider.io) {
|
||||||
|
secureStorage.store(key = createCustomerAddressKey(userWalletId), value = customerWalletAddress)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String? {
|
||||||
|
return withContext(dispatcherProvider.io) {
|
||||||
|
secureStorage.getAsString(createCustomerAddressKey(userWalletId))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens) =
|
override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens) =
|
||||||
withContext(dispatcherProvider.io) {
|
withContext(dispatcherProvider.io) {
|
||||||
val json = tokensAdapter.toJson(tokens)
|
val json = tokensAdapter.toJson(tokens)
|
||||||
|
|
@ -67,14 +80,33 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
||||||
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
|
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun getAddToWalletDone(customerWalletAddress: String): Boolean {
|
||||||
|
return withContext(dispatcherProvider.io) {
|
||||||
|
appPreferencesStore.getSyncOrNull(
|
||||||
|
key = PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress),
|
||||||
|
) == true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun storeAddToWalletDone(customerWalletAddress: String, isDone: Boolean) {
|
||||||
|
withContext(dispatcherProvider.io) {
|
||||||
|
appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), isDone)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||||
}
|
}
|
||||||
|
|
||||||
override suspend fun clearAll(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) =
|
||||||
secureStorage.delete(createKey(customerWalletAddress))
|
withContext(dispatcherProvider.io) {
|
||||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
secureStorage.delete(createCustomerAddressKey(userWalletId))
|
||||||
}
|
secureStorage.delete(createKey(customerWalletAddress))
|
||||||
|
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||||
|
appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun createCustomerAddressKey(userWalletId: UserWalletId): String = userWalletId.stringValue
|
||||||
|
|
||||||
private fun createKey(address: String): String = "${DEFAULT_KEY}_$address"
|
private fun createKey(address: String): String = "${DEFAULT_KEY}_$address"
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,14 +1,18 @@
|
||||||
package com.tangem.tap.di
|
package com.tangem.tap.di
|
||||||
|
|
||||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
import com.tangem.datasource.api.moonpay.MoonPayApi
|
||||||
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
|
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||||
import com.tangem.domain.card.ScanCardUseCase
|
import com.tangem.domain.card.ScanCardUseCase
|
||||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||||
import com.tangem.domain.exchange.RampStateManager
|
import com.tangem.domain.exchange.RampStateManager
|
||||||
|
import com.tangem.domain.express.ExpressServiceFetcher
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||||
|
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
|
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
|
||||||
import com.tangem.tap.network.exchangeServices.DefaultRampManager
|
import com.tangem.tap.network.exchangeServices.DefaultRampManager
|
||||||
|
import com.tangem.tap.network.exchangeServices.SellService
|
||||||
|
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
import com.tangem.utils.Provider
|
import com.tangem.utils.Provider
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -43,17 +47,15 @@ internal object ActivityModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideDefaultRampManager(
|
fun provideDefaultRampManager(
|
||||||
appStateHolder: AppStateHolder,
|
appStateHolder: AppStateHolder,
|
||||||
expressServiceLoader: ExpressServiceLoader,
|
expressServiceFetcher: ExpressServiceFetcher,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
excludedBlockchains: ExcludedBlockchains,
|
|
||||||
dispatchers: CoroutineDispatcherProvider,
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
): RampStateManager {
|
): RampStateManager {
|
||||||
return DefaultRampManager(
|
return DefaultRampManager(
|
||||||
sellService = Provider { requireNotNull(appStateHolder.sellService) },
|
sellService = Provider { requireNotNull(appStateHolder.sellService) },
|
||||||
expressServiceLoader = expressServiceLoader,
|
expressServiceFetcher = expressServiceFetcher,
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
dispatchers = dispatchers,
|
dispatchers = dispatchers,
|
||||||
excludedBlockchains = excludedBlockchains,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -63,4 +65,19 @@ internal object ActivityModule {
|
||||||
fun provideActivityDelayedWorkCoroutineScope(): CoroutineScope {
|
fun provideActivityDelayedWorkCoroutineScope(): CoroutineScope {
|
||||||
return CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
return CoroutineScope(SupervisorJob() + Dispatchers.IO)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideExchangeService(
|
||||||
|
environmentConfigStorage: EnvironmentConfigStorage,
|
||||||
|
getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||||
|
moonPayApi: MoonPayApi,
|
||||||
|
): SellService {
|
||||||
|
return MoonPayService(
|
||||||
|
api = moonPayApi,
|
||||||
|
apiKeyProvider = Provider { environmentConfigStorage.getConfigSync().moonPayApiKey },
|
||||||
|
secretKeyProvider = Provider { environmentConfigStorage.getConfigSync().moonPayApiSecretKey },
|
||||||
|
userWalletProvider = { getSelectedWalletUseCase.sync().getOrNull() },
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -4,9 +4,9 @@ import android.content.Context
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Vibrator
|
import android.os.Vibrator
|
||||||
import android.os.VibratorManager
|
import android.os.VibratorManager
|
||||||
import com.tangem.tap.common.haptic.DefaultVibratorHapticManager
|
|
||||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||||
|
import com.tangem.tap.common.haptic.DefaultVibratorHapticManager
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -22,10 +22,14 @@ class HapticModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideHapticManager(@ApplicationContext context: Context): VibratorHapticManager {
|
fun provideHapticManager(@ApplicationContext context: Context): VibratorHapticManager {
|
||||||
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
val vibrator = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
|
||||||
val vibratorManager = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE) as VibratorManager
|
val service = context.getSystemService(Context.VIBRATOR_MANAGER_SERVICE)
|
||||||
|
val vibratorManager = requireNotNull(service as? VibratorManager) {
|
||||||
|
"VibratorManager not available"
|
||||||
|
}
|
||||||
vibratorManager.defaultVibrator
|
vibratorManager.defaultVibrator
|
||||||
} else {
|
} else {
|
||||||
context.getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
|
val service = context.getSystemService(Context.VIBRATOR_SERVICE)
|
||||||
|
requireNotNull(service as? Vibrator) { "Vibrator service not available" }
|
||||||
}
|
}
|
||||||
|
|
||||||
return if (vibrator.hasVibrator()) {
|
return if (vibrator.hasVibrator()) {
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@ import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||||
import com.tangem.sdk.api.TangemSdkManager
|
import com.tangem.sdk.api.TangemSdkManager
|
||||||
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
||||||
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
import com.tangem.tap.domain.sdk.impl.MockTangemSdkManager
|
||||||
|
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||||
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
import com.tangem.tap.domain.visa.VisaCardScanHandler
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -27,6 +28,7 @@ internal class TangemSdkManagerModule {
|
||||||
cardSdkConfigRepository: CardSdkConfigRepository,
|
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||||
visaCardScanHandler: VisaCardScanHandler,
|
visaCardScanHandler: VisaCardScanHandler,
|
||||||
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||||
|
tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||||
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||||
): TangemSdkManager {
|
): TangemSdkManager {
|
||||||
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||||
|
|
@ -37,6 +39,7 @@ internal class TangemSdkManagerModule {
|
||||||
resources = context.resources,
|
resources = context.resources,
|
||||||
visaCardScanHandler = visaCardScanHandler,
|
visaCardScanHandler = visaCardScanHandler,
|
||||||
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
||||||
|
tangemPayChallengeTaskFactory = tangemPayChallengeTaskFactory,
|
||||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,9 @@
|
||||||
package com.tangem.tap.di.analytics
|
package com.tangem.tap.di.analytics
|
||||||
|
|
||||||
|
import com.tangem.core.abtests.manager.ABTestsManager
|
||||||
import com.tangem.core.analytics.AppInstanceIdProvider
|
import com.tangem.core.analytics.AppInstanceIdProvider
|
||||||
import com.tangem.core.analytics.utils.AnalyticsContextProxy
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase
|
import com.tangem.tap.common.analytics.DefaultTrackingContextProxy
|
||||||
import com.tangem.tap.common.analytics.DefaultAnalyticsContextProxy
|
|
||||||
import com.tangem.tap.common.analytics.DefaultChangeCardAnalyticsContextUseCase
|
|
||||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAppInstanceIdProvider
|
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAppInstanceIdProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
|
|
@ -18,13 +17,8 @@ internal object AnalyticsModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideChangeCardAnalyticsContextUseCase(): ChangeCardAnalyticsContextUseCase {
|
fun provideAnalyticsContextProxy(abtestsManager: ABTestsManager): TrackingContextProxy =
|
||||||
return DefaultChangeCardAnalyticsContextUseCase()
|
DefaultTrackingContextProxy(abtestsManager)
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideAnalyticsContextProxy(): AnalyticsContextProxy = DefaultAnalyticsContextProxy()
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
|
||||||
|
|
@ -3,8 +3,11 @@ package com.tangem.tap.di.domain
|
||||||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||||
|
import com.tangem.domain.account.status.usecase.RecoverCryptoPortfolioUseCase
|
||||||
|
import com.tangem.domain.account.status.utils.CryptoCurrencyBalanceFetcher
|
||||||
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
import com.tangem.domain.account.tokens.MainAccountTokensMigration
|
||||||
import com.tangem.domain.account.usecase.*
|
import com.tangem.domain.account.usecase.*
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
import dagger.Provides
|
import dagger.Provides
|
||||||
import dagger.hilt.InstallIn
|
import dagger.hilt.InstallIn
|
||||||
|
|
@ -50,10 +53,12 @@ internal object AccountDomainModule {
|
||||||
fun provideRecoverCryptoPortfolioUseCase(
|
fun provideRecoverCryptoPortfolioUseCase(
|
||||||
accountsCRUDRepository: AccountsCRUDRepository,
|
accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
mainAccountTokensMigration: MainAccountTokensMigration,
|
mainAccountTokensMigration: MainAccountTokensMigration,
|
||||||
|
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||||
): RecoverCryptoPortfolioUseCase {
|
): RecoverCryptoPortfolioUseCase {
|
||||||
return RecoverCryptoPortfolioUseCase(
|
return RecoverCryptoPortfolioUseCase(
|
||||||
crudRepository = accountsCRUDRepository,
|
crudRepository = accountsCRUDRepository,
|
||||||
mainAccountTokensMigration = mainAccountTokensMigration,
|
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||||
|
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -82,4 +87,16 @@ internal object AccountDomainModule {
|
||||||
accountsFeatureToggles = accountsFeatureToggles,
|
accountsFeatureToggles = accountsFeatureToggles,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideApplyAccountListSortingUseCase(
|
||||||
|
accountsCRUDRepository: AccountsCRUDRepository,
|
||||||
|
dispatchers: CoroutineDispatcherProvider,
|
||||||
|
): ApplyAccountListSortingUseCase {
|
||||||
|
return ApplyAccountListSortingUseCase(
|
||||||
|
accountsCRUDRepository = accountsCRUDRepository,
|
||||||
|
dispatchers = dispatchers,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -33,7 +33,7 @@ internal object CardDomainModule {
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideIsDemoCardUseCase(): IsDemoCardUseCase = IsDemoCardUseCase(config = DemoConfig())
|
fun provideIsDemoCardUseCase(): IsDemoCardUseCase = IsDemoCardUseCase(config = DemoConfig)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.domain.hotwallet.GetAccessCodeSkippedUseCase
|
||||||
|
import com.tangem.domain.hotwallet.SetAccessCodeSkippedUseCase
|
||||||
|
import com.tangem.domain.hotwallet.repository.HotWalletRepository
|
||||||
|
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 HotWalletDomainModule {
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideGetAccessCodeSkippedUseCase(hotWalletRepository: HotWalletRepository): GetAccessCodeSkippedUseCase {
|
||||||
|
return GetAccessCodeSkippedUseCase(hotWalletRepository)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideSetAccessCodeSkippedUseCase(hotWalletRepository: HotWalletRepository): SetAccessCodeSkippedUseCase {
|
||||||
|
return SetAccessCodeSkippedUseCase(hotWalletRepository)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
package com.tangem.tap.di.domain
|
package com.tangem.tap.di.domain
|
||||||
|
|
||||||
|
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||||
|
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||||
import com.tangem.domain.nft.*
|
import com.tangem.domain.nft.*
|
||||||
import com.tangem.domain.nft.repository.NFTRepository
|
import com.tangem.domain.nft.repository.NFTRepository
|
||||||
|
|
@ -24,9 +26,13 @@ internal object NFTDomainModule {
|
||||||
fun providesGetNFTCollectionsUseCase(
|
fun providesGetNFTCollectionsUseCase(
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
nftRepository: NFTRepository,
|
nftRepository: NFTRepository,
|
||||||
|
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||||
|
accountsFeatureToggles: AccountsFeatureToggles,
|
||||||
): GetNFTCollectionsUseCase = GetNFTCollectionsUseCase(
|
): GetNFTCollectionsUseCase = GetNFTCollectionsUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
nftRepository = nftRepository,
|
nftRepository = nftRepository,
|
||||||
|
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||||
|
accountsFeatureToggles = accountsFeatureToggles,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
@ -60,10 +66,12 @@ internal object NFTDomainModule {
|
||||||
@Singleton
|
@Singleton
|
||||||
fun providesGetNFTAvailableNetworksUseCase(
|
fun providesGetNFTAvailableNetworksUseCase(
|
||||||
nftRepository: NFTRepository,
|
nftRepository: NFTRepository,
|
||||||
|
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||||
currenciesRepository: CurrenciesRepository,
|
currenciesRepository: CurrenciesRepository,
|
||||||
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
||||||
currenciesRepository = currenciesRepository,
|
currenciesRepository = currenciesRepository,
|
||||||
nftRepository = nftRepository,
|
nftRepository = nftRepository,
|
||||||
|
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||||
)
|
)
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
|
|
|
||||||
|
|
@ -179,20 +179,6 @@ internal object OnrampDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideGetOnrampV2QuotesUseCase(
|
|
||||||
settingsRepository: SettingsRepository,
|
|
||||||
onrampRepository: OnrampRepository,
|
|
||||||
onrampErrorResolver: OnrampErrorResolver,
|
|
||||||
): GetOnrampV2QuotesUseCase {
|
|
||||||
return GetOnrampV2QuotesUseCase(
|
|
||||||
settingsRepository = settingsRepository,
|
|
||||||
repository = onrampRepository,
|
|
||||||
errorResolver = onrampErrorResolver,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetOnrampProviderWithQuoteUseCase(
|
fun provideGetOnrampProviderWithQuoteUseCase(
|
||||||
|
|
|
||||||
|
|
@ -20,10 +20,7 @@ import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
||||||
import com.tangem.domain.tokens.*
|
import com.tangem.domain.tokens.*
|
||||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
import com.tangem.domain.tokens.repository.*
|
||||||
import com.tangem.domain.tokens.repository.CurrencyChecksRepository
|
|
||||||
import com.tangem.domain.tokens.repository.TokenReceiveWarningsViewedRepository
|
|
||||||
import com.tangem.domain.tokens.repository.YieldSupplyWarningsViewedRepository
|
|
||||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||||
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
import com.tangem.tap.domain.tokens.DefaultTokensFeatureToggles
|
||||||
|
|
@ -59,24 +56,6 @@ internal object TokensDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideFetchTokenListUseCase(
|
|
||||||
currenciesRepository: CurrenciesRepository,
|
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
|
||||||
stakingIdFactory: StakingIdFactory,
|
|
||||||
): FetchTokenListUseCase {
|
|
||||||
return FetchTokenListUseCase(
|
|
||||||
currenciesRepository = currenciesRepository,
|
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
|
||||||
stakingIdFactory = stakingIdFactory,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideFetchPendingTransactionsUseCase(
|
fun provideFetchPendingTransactionsUseCase(
|
||||||
|
|
@ -183,24 +162,6 @@ internal object TokensDomainModule {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@Provides
|
|
||||||
@Singleton
|
|
||||||
fun provideFetchCardTokenListUseCase(
|
|
||||||
currenciesRepository: CurrenciesRepository,
|
|
||||||
multiNetworkStatusFetcher: MultiNetworkStatusFetcher,
|
|
||||||
multiQuoteStatusFetcher: MultiQuoteStatusFetcher,
|
|
||||||
multiYieldBalanceFetcher: MultiYieldBalanceFetcher,
|
|
||||||
stakingIdFactory: StakingIdFactory,
|
|
||||||
): FetchCardTokenListUseCase {
|
|
||||||
return FetchCardTokenListUseCase(
|
|
||||||
currenciesRepository = currenciesRepository,
|
|
||||||
multiNetworkStatusFetcher = multiNetworkStatusFetcher,
|
|
||||||
multiQuoteStatusFetcher = multiQuoteStatusFetcher,
|
|
||||||
multiYieldBalanceFetcher = multiYieldBalanceFetcher,
|
|
||||||
stakingIdFactory = stakingIdFactory,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideGetCryptoCurrencyUseCase(
|
fun provideGetCryptoCurrencyUseCase(
|
||||||
|
|
|
||||||
|
|
@ -28,7 +28,7 @@ internal object TransactionDomainModule {
|
||||||
fun provideGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetFeeUseCase {
|
fun provideGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetFeeUseCase {
|
||||||
return GetFeeUseCase(
|
return GetFeeUseCase(
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
demoConfig = DemoConfig(),
|
demoConfig = DemoConfig,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -48,7 +48,7 @@ internal object TransactionDomainModule {
|
||||||
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
|
tangemHotWalletSignerFactory: TangemHotWalletSigner.Factory,
|
||||||
): SendTransactionUseCase {
|
): SendTransactionUseCase {
|
||||||
return SendTransactionUseCase(
|
return SendTransactionUseCase(
|
||||||
demoConfig = DemoConfig(),
|
demoConfig = DemoConfig,
|
||||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||||
transactionRepository = transactionRepository,
|
transactionRepository = transactionRepository,
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
|
|
@ -126,7 +126,7 @@ internal object TransactionDomainModule {
|
||||||
fun provideEstimateFeeUseCase(walletManagersFacade: WalletManagersFacade): EstimateFeeUseCase {
|
fun provideEstimateFeeUseCase(walletManagersFacade: WalletManagersFacade): EstimateFeeUseCase {
|
||||||
return EstimateFeeUseCase(
|
return EstimateFeeUseCase(
|
||||||
walletManagersFacade = walletManagersFacade,
|
walletManagersFacade = walletManagersFacade,
|
||||||
demoConfig = DemoConfig(),
|
demoConfig = DemoConfig,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -483,4 +483,12 @@ internal object WalletsDomainModule {
|
||||||
dispatchers = dispatcherProvider,
|
dispatchers = dispatcherProvider,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideHasSecuredWalletsUseCase(
|
||||||
|
userWalletsListRepository: UserWalletsListRepository,
|
||||||
|
): HasSecuredWalletsUseCase {
|
||||||
|
return HasSecuredWalletsUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -2,11 +2,11 @@ package com.tangem.tap.domain
|
||||||
|
|
||||||
import com.tangem.blockchain.common.Token
|
import com.tangem.blockchain.common.Token
|
||||||
import com.tangem.blockchain.common.Wallet
|
import com.tangem.blockchain.common.Wallet
|
||||||
import com.tangem.core.analytics.Analytics
|
|
||||||
import com.tangem.domain.common.extensions.withMainContext
|
import com.tangem.domain.common.extensions.withMainContext
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.tap.common.extensions.setContext
|
import com.tangem.tap.common.extensions.inject
|
||||||
import com.tangem.tap.common.redux.global.GlobalAction
|
import com.tangem.tap.common.redux.global.GlobalAction
|
||||||
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
import com.tangem.tap.store
|
import com.tangem.tap.store
|
||||||
import com.tangem.tap.tangemSdkManager
|
import com.tangem.tap.tangemSdkManager
|
||||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||||
|
|
@ -30,11 +30,12 @@ class TapWalletManager(
|
||||||
// ensuring that only one job is active at any given time.
|
// ensuring that only one job is active at any given time.
|
||||||
loadUserWalletDataJob = CoroutineScope(dispatchers.io)
|
loadUserWalletDataJob = CoroutineScope(dispatchers.io)
|
||||||
.launch { loadUserWalletData(userWallet) }
|
.launch { loadUserWalletData(userWallet) }
|
||||||
.also { it.join() }
|
.apply { join() }
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun loadUserWalletData(userWallet: UserWallet) {
|
private suspend fun loadUserWalletData(userWallet: UserWallet) {
|
||||||
Analytics.setContext(userWallet)
|
val trackingContextProxy = store.inject(DaggerGraphState::trackingContextProxy)
|
||||||
|
trackingContextProxy.setContext(userWallet)
|
||||||
|
|
||||||
if (userWallet is UserWallet.Cold) {
|
if (userWallet is UserWallet.Cold) {
|
||||||
val scanResponse = userWallet.scanResponse
|
val scanResponse = userWallet.scanResponse
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@ import com.tangem.core.analytics.models.AnalyticsEvent
|
||||||
import com.tangem.core.analytics.models.AnalyticsParam
|
import com.tangem.core.analytics.models.AnalyticsParam
|
||||||
import com.tangem.core.analytics.models.Basic
|
import com.tangem.core.analytics.models.Basic
|
||||||
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
import com.tangem.core.analytics.models.event.OnboardingAnalyticsEvent
|
||||||
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||||
import com.tangem.core.decompose.ui.UiMessageSender
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
|
|
@ -47,6 +48,7 @@ import javax.inject.Singleton
|
||||||
internal class LegacyScanProcessor @Inject constructor(
|
internal class LegacyScanProcessor @Inject constructor(
|
||||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
|
private val trackingContextProxy: TrackingContextProxy,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
suspend fun scan(
|
suspend fun scan(
|
||||||
|
|
@ -118,16 +120,14 @@ internal class LegacyScanProcessor @Inject constructor(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) {
|
private fun sendAnalytics(analyticsEvent: AnalyticsEvent, scanResponse: ScanResponse) {
|
||||||
analyticsEvent?.let { event ->
|
// this workaround needed to send CardWasScannedEvent without adding a context
|
||||||
// this workaround needed to send CardWasScannedEvent without adding a context
|
val interceptor = CardContextInterceptor(scanResponse)
|
||||||
val interceptor = CardContextInterceptor(scanResponse)
|
val params = analyticsEvent.params.toMutableMap()
|
||||||
val params = event.params.toMutableMap()
|
interceptor.intercept(params)
|
||||||
interceptor.intercept(params)
|
analyticsEvent.params = params.toMap()
|
||||||
event.params = params.toMap()
|
|
||||||
|
|
||||||
Analytics.send(event)
|
Analytics.send(analyticsEvent)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: [REDACTED_JIRA]
|
// TODO: [REDACTED_JIRA]
|
||||||
|
|
@ -211,7 +211,7 @@ internal class LegacyScanProcessor @Inject constructor(
|
||||||
},
|
},
|
||||||
) {
|
) {
|
||||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||||
Analytics.addContext(scanResponse)
|
trackingContextProxy.addContext(scanResponse)
|
||||||
onWalletNotCreated()
|
onWalletNotCreated()
|
||||||
navigateTo(
|
navigateTo(
|
||||||
AppRoute.Onboarding(
|
AppRoute.Onboarding(
|
||||||
|
|
@ -220,7 +220,7 @@ internal class LegacyScanProcessor @Inject constructor(
|
||||||
),
|
),
|
||||||
) { onProgressStateChange(it) }
|
) { onProgressStateChange(it) }
|
||||||
} else {
|
} else {
|
||||||
Analytics.setContext(scanResponse)
|
trackingContextProxy.setContext(scanResponse)
|
||||||
|
|
||||||
val wasTwinsOnboardingShown =
|
val wasTwinsOnboardingShown =
|
||||||
store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase).invokeSync()
|
store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase).invokeSync()
|
||||||
|
|
|
||||||
|
|
@ -3,15 +3,12 @@ package com.tangem.tap.domain.scanCard.chains
|
||||||
import arrow.core.left
|
import arrow.core.left
|
||||||
import arrow.core.right
|
import arrow.core.right
|
||||||
import com.tangem.common.routing.AppRoute
|
import com.tangem.common.routing.AppRoute
|
||||||
import com.tangem.core.analytics.Analytics
|
|
||||||
import com.tangem.domain.card.ScanCardException
|
import com.tangem.domain.card.ScanCardException
|
||||||
import com.tangem.domain.card.common.util.twinsIsTwinned
|
import com.tangem.domain.card.common.util.twinsIsTwinned
|
||||||
import com.tangem.domain.core.chain.Chain
|
import com.tangem.domain.core.chain.Chain
|
||||||
import com.tangem.domain.core.chain.ResultChain
|
import com.tangem.domain.core.chain.ResultChain
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.tap.common.extensions.addContext
|
|
||||||
import com.tangem.tap.common.extensions.inject
|
import com.tangem.tap.common.extensions.inject
|
||||||
import com.tangem.tap.common.extensions.setContext
|
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||||
|
|
@ -34,9 +31,11 @@ class CheckForOnboardingChain(
|
||||||
) : ResultChain<ScanCardException, ScanResponse>() {
|
) : ResultChain<ScanCardException, ScanResponse>() {
|
||||||
|
|
||||||
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
|
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
|
||||||
|
val trackingContextProxy = store.inject(DaggerGraphState::trackingContextProxy)
|
||||||
|
|
||||||
return when {
|
return when {
|
||||||
OnboardingHelper.isOnboardingCase(previousChainResult) -> {
|
OnboardingHelper.isOnboardingCase(previousChainResult) -> {
|
||||||
Analytics.addContext(previousChainResult)
|
trackingContextProxy.addContext(previousChainResult)
|
||||||
ScanChainException.OnboardingNeeded(
|
ScanChainException.OnboardingNeeded(
|
||||||
AppRoute.Onboarding(
|
AppRoute.Onboarding(
|
||||||
scanResponse = previousChainResult,
|
scanResponse = previousChainResult,
|
||||||
|
|
@ -45,7 +44,7 @@ class CheckForOnboardingChain(
|
||||||
).left()
|
).left()
|
||||||
}
|
}
|
||||||
else -> {
|
else -> {
|
||||||
Analytics.setContext(previousChainResult)
|
trackingContextProxy.setContext(previousChainResult)
|
||||||
|
|
||||||
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
|
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
|
||||||
.invokeSync()
|
.invokeSync()
|
||||||
|
|
|
||||||
|
|
@ -18,13 +18,13 @@ import com.tangem.core.res.getStringSafe
|
||||||
import com.tangem.crypto.bip39.DefaultMnemonic
|
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
|
||||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
import com.tangem.domain.models.scan.ScanResponse
|
import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.domain.models.wallet.UserWalletId
|
import com.tangem.domain.models.wallet.UserWalletId
|
||||||
import com.tangem.domain.visa.model.*
|
import com.tangem.domain.visa.model.*
|
||||||
|
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||||
import com.tangem.operations.ScanTask
|
import com.tangem.operations.ScanTask
|
||||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||||
|
|
@ -44,6 +44,7 @@ import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
||||||
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
||||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||||
|
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||||
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
|
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
|
||||||
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
||||||
|
|
@ -62,6 +63,7 @@ internal class DefaultTangemSdkManager(
|
||||||
private val resources: Resources,
|
private val resources: Resources,
|
||||||
private val visaCardScanHandler: VisaCardScanHandler,
|
private val visaCardScanHandler: VisaCardScanHandler,
|
||||||
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||||
|
private val tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||||
) : TangemSdkManager {
|
) : TangemSdkManager {
|
||||||
|
|
||||||
|
|
@ -76,13 +78,12 @@ internal class DefaultTangemSdkManager(
|
||||||
secureStorage = tangemSdk.secureStorage,
|
secureStorage = tangemSdk.secureStorage,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
override val needEnrollBiometrics: Boolean
|
||||||
|
get() = tangemSdk.authenticationManager.needEnrollBiometrics
|
||||||
|
|
||||||
override val canUseBiometry: Boolean
|
override val canUseBiometry: Boolean
|
||||||
get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics
|
get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics
|
||||||
|
|
||||||
override val needEnrollBiometrics: Boolean
|
|
||||||
get() = tangemSdk.authenticationManager.needEnrollBiometrics
|
|
||||||
|
|
||||||
override val keystoreManager: KeystoreManager
|
override val keystoreManager: KeystoreManager
|
||||||
get() = tangemSdk.keystoreManager
|
get() = tangemSdk.keystoreManager
|
||||||
|
|
||||||
|
|
@ -511,6 +512,18 @@ internal class DefaultTangemSdkManager(
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun tangemPayProduceInitialCredentials(
|
||||||
|
cardId: String,
|
||||||
|
): CompletionResult<TangemPayInitialCredentials> {
|
||||||
|
return coroutineScope {
|
||||||
|
runTaskAsyncReturnOnMain(
|
||||||
|
runnable = tangemPayChallengeTaskFactory.create(coroutineScope = this),
|
||||||
|
cardId = cardId,
|
||||||
|
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
||||||
|
|
@ -213,5 +213,11 @@ class MockTangemSdkManager(
|
||||||
error("Not implemented")
|
error("Not implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun tangemPayProduceInitialCredentials(
|
||||||
|
cardId: String,
|
||||||
|
): CompletionResult<TangemPayInitialCredentials> {
|
||||||
|
error("Not implemented")
|
||||||
|
}
|
||||||
|
|
||||||
// endregion
|
// endregion
|
||||||
}
|
}
|
||||||
|
|
@ -1,7 +1,11 @@
|
||||||
package com.tangem.tap.domain.sdk.mocks.content
|
package com.tangem.tap.domain.sdk.mocks.content
|
||||||
|
|
||||||
import com.tangem.common.SuccessResponse
|
import com.tangem.common.SuccessResponse
|
||||||
import com.tangem.common.card.*
|
import com.tangem.common.card.Card
|
||||||
|
import com.tangem.common.card.CardWallet
|
||||||
|
import com.tangem.common.card.EllipticCurve
|
||||||
|
import com.tangem.common.card.EncryptionMode
|
||||||
|
import com.tangem.common.card.FirmwareVersion
|
||||||
import com.tangem.common.extensions.ByteArrayKey
|
import com.tangem.common.extensions.ByteArrayKey
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
|
|
@ -125,10 +129,18 @@ object WalletMockContent : MockContent {
|
||||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
),
|
),
|
||||||
|
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
),
|
||||||
DerivationPath("m/44'/144'/0'/0/0") to ExtendedPublicKey(
|
DerivationPath("m/44'/144'/0'/0/0") to ExtendedPublicKey(
|
||||||
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
),
|
),
|
||||||
|
DerivationPath("m/44'/501'/0'") to ExtendedPublicKey(
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
extendedPublicKey = ExtendedPublicKey(
|
extendedPublicKey = ExtendedPublicKey(
|
||||||
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
publicKey = byteArrayOf(2, -109, 28, -27, -124, -58, -97, -61, 43, -84, 90, -9, -5, 4, 90, 17, 112, -125, -108, 44, 19, -79, -60, -23, 34, -20, -20, 61, 84, 113, 120, -90, -5),
|
||||||
|
|
@ -235,6 +247,20 @@ object WalletMockContent : MockContent {
|
||||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
childNumber = 0,
|
childNumber = 0,
|
||||||
),
|
),
|
||||||
|
DerivationPath("m/44'/1729'/0'/0'") to ExtendedPublicKey( // Tezos
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/111111'/0'/0/0") to ExtendedPublicKey( // Kaspa
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
ByteArrayKey(
|
ByteArrayKey(
|
||||||
|
|
@ -244,14 +270,49 @@ object WalletMockContent : MockContent {
|
||||||
ExtendedPublicKeysMap(
|
ExtendedPublicKeysMap(
|
||||||
mapOf(
|
mapOf(
|
||||||
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano
|
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano
|
||||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
depth = 0,
|
depth = 0,
|
||||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
childNumber = 0,
|
childNumber = 0,
|
||||||
),
|
),
|
||||||
DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended
|
DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended
|
||||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/501'/0'") to ExtendedPublicKey( // Solana
|
||||||
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/354'/0'/0'/0'") to ExtendedPublicKey( // Polkadot
|
||||||
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/434'/0'/0'/0'") to ExtendedPublicKey( // Kusama
|
||||||
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/643'/0'/0'/0'") to ExtendedPublicKey( // Azero
|
||||||
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/148'/0'") to ExtendedPublicKey( // Stellar
|
||||||
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
depth = 0,
|
depth = 0,
|
||||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
|
@ -296,9 +357,16 @@ object WalletMockContent : MockContent {
|
||||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
childNumber = 0,
|
childNumber = 0,
|
||||||
),
|
),
|
||||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // xrp
|
DerivationPath("m/44'/1729'/0'/0'") to ExtendedPublicKey( // Tezos
|
||||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/111111'/0'/0/0") to ExtendedPublicKey( // Kaspa
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
depth = 0,
|
depth = 0,
|
||||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
childNumber = 0,
|
childNumber = 0,
|
||||||
|
|
@ -312,19 +380,54 @@ object WalletMockContent : MockContent {
|
||||||
ExtendedPublicKeysMap(
|
ExtendedPublicKeysMap(
|
||||||
mapOf(
|
mapOf(
|
||||||
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano
|
DerivationPath("m/1852'/1815'/0'/0/0") to ExtendedPublicKey( // cardano
|
||||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
depth = 0,
|
depth = 0,
|
||||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
childNumber = 0,
|
childNumber = 0,
|
||||||
),
|
),
|
||||||
DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended
|
DerivationPath("m/1852'/1815'/0'/2/0") to ExtendedPublicKey( // cardano extended
|
||||||
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82, -67),
|
publicKey = byteArrayOf(2, 34, 6, 119, -106, 5, -119, 111, -22, 8, 23, -108, -72, -56, 6, 77, -17, -61, -101, -85, 16, 28, 18, 3, -3, -89, -81, -108, 48, -7, -86, -82),
|
||||||
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
chainCode = byteArrayOf(-75, 55, 107, -106, -37, -81, -15, 72, -102, 94, 55, -39, 9, -112, 1, 90, -50, 103, 53, 120, -92, -36, -85, -39, -65, 1, 88, 46, 92, 104, -13, -109),
|
||||||
depth = 0,
|
depth = 0,
|
||||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
childNumber = 0,
|
childNumber = 0,
|
||||||
),
|
),
|
||||||
|
DerivationPath("m/44'/501'/0'") to ExtendedPublicKey( // Solana
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/354'/0'/0'/0'") to ExtendedPublicKey( // Polkadot
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/434'/0'/0'/0'") to ExtendedPublicKey( // Kusama
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/643'/0'/0'/0'") to ExtendedPublicKey( // Azero
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
|
DerivationPath("m/44'/148'/0'") to ExtendedPublicKey( // Stellar
|
||||||
|
publicKey = byteArrayOf(2, -40, 115, -15, 80, 104, 100, -29, 80, 29, 112, -35, -54, 64, -98, 45, 115, 108, 93, 113, -71, 89, 17, 72, 98, 21, 126, 82, -50, 50, -12, -87, -62),
|
||||||
|
chainCode = byteArrayOf(-27, 56, 57, 54, 27, -40, 65, 109, 119, -54, -94, 88, -29, -115, -45, -112, -31, 57, 53, 56, 118, 87, 34, -16, -64, -45, 105, 77, 91, -106, -92, 41),
|
||||||
|
depth = 0,
|
||||||
|
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||||
|
childNumber = 0,
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|
|
||||||
|
|
@ -14,10 +14,11 @@ import com.tangem.common.map
|
||||||
import com.tangem.crypto.bip39.Mnemonic
|
import com.tangem.crypto.bip39.Mnemonic
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.domain.card.CardTypesResolver
|
import com.tangem.domain.card.CardTypesResolver
|
||||||
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
|
||||||
import com.tangem.domain.card.common.TapWorkarounds.isTestCard
|
import com.tangem.domain.card.common.TapWorkarounds.isTestCard
|
||||||
import com.tangem.domain.card.configs.CardConfig
|
import com.tangem.domain.card.configs.CardConfig
|
||||||
|
import com.tangem.domain.demo.models.DemoConfig
|
||||||
import com.tangem.domain.models.scan.CardDTO
|
import com.tangem.domain.models.scan.CardDTO
|
||||||
|
import com.tangem.domain.wallets.derivations.DerivationStyleProvider
|
||||||
import com.tangem.operations.backup.PrimaryCard
|
import com.tangem.operations.backup.PrimaryCard
|
||||||
import com.tangem.operations.backup.StartPrimaryCardLinkingCommand
|
import com.tangem.operations.backup.StartPrimaryCardLinkingCommand
|
||||||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||||
|
|
@ -60,7 +61,13 @@ class CreateProductWalletTask(
|
||||||
val cardDto = CardDTO(card)
|
val cardDto = CardDTO(card)
|
||||||
|
|
||||||
val commandProcessor = when {
|
val commandProcessor = when {
|
||||||
cardTypesResolver.isTangemNote() -> CreateWalletTangemNote(cardTypesResolver)
|
/**
|
||||||
|
* @workaround isDemoNoteAsMultiwallet
|
||||||
|
* There were produced 20k Note demo cards that should work like multiwallet (except Onboarding)
|
||||||
|
* for that reasons we've just added some specific checks for their BatchId
|
||||||
|
*/
|
||||||
|
DemoConfig.isDemoNoteAsMultiwallet(card.cardId) || cardTypesResolver.isTangemNote() ->
|
||||||
|
CreateWalletTangemNote(cardTypesResolver)
|
||||||
cardTypesResolver.isTangemTwins() ->
|
cardTypesResolver.isTangemTwins() ->
|
||||||
throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
|
throw UnsupportedOperationException("Use the TwinCardsManager to create a wallet")
|
||||||
|
|
||||||
|
|
@ -374,7 +381,7 @@ private class CreateWalletTangemWallet(
|
||||||
|
|
||||||
private fun getBlockchains(cardId: String, card: CardDTO): List<Blockchain> {
|
private fun getBlockchains(cardId: String, card: CardDTO): List<Blockchain> {
|
||||||
return when {
|
return when {
|
||||||
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains.toList()
|
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.getDemoBlockchains(cardId).toList()
|
||||||
card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet)
|
card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet)
|
||||||
else -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
else -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -38,7 +38,7 @@ internal class DerivationsFinder(
|
||||||
getBlockchains(userWalletId)
|
getBlockchains(userWalletId)
|
||||||
}.ifEmpty {
|
}.ifEmpty {
|
||||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||||
getDemoBlockchains(derivationStyle)
|
getDemoBlockchains(derivationStyle, card.cardId)
|
||||||
} else {
|
} else {
|
||||||
getDefaultBlockchains(derivationStyle)
|
getDefaultBlockchains(derivationStyle)
|
||||||
}
|
}
|
||||||
|
|
@ -77,8 +77,8 @@ internal class DerivationsFinder(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move to user wallet config
|
// TODO: Move to user wallet config
|
||||||
private fun getDemoBlockchains(derivationStyle: DerivationStyle?): MutableSet<BlockchainToDerive> {
|
private fun getDemoBlockchains(derivationStyle: DerivationStyle?, cardId: String): MutableSet<BlockchainToDerive> {
|
||||||
return DemoHelper.config.demoBlockchains.mapToBlockchainsWithDerivations(derivationStyle)
|
return DemoHelper.config.getDemoBlockchains(cardId).mapToBlockchainsWithDerivations(derivationStyle)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Move to user wallet config
|
// TODO: Move to user wallet config
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,6 @@ import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
|
||||||
*
|
*
|
||||||
[REDACTED_AUTHOR]
|
[REDACTED_AUTHOR]
|
||||||
*/
|
*/
|
||||||
// TODO remove it after test after resolve [REDACTED_JIRA]
|
|
||||||
internal class ResetBackupCardTask(
|
internal class ResetBackupCardTask(
|
||||||
private val userWalletId: UserWalletId,
|
private val userWalletId: UserWalletId,
|
||||||
) : CardSessionRunnable<Boolean> {
|
) : CardSessionRunnable<Boolean> {
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package com.tangem.tap.domain.tasks.visa
|
||||||
|
|
||||||
|
import arrow.core.getOrElse
|
||||||
|
import com.tangem.common.CompletionResult
|
||||||
|
import com.tangem.common.card.CardWallet
|
||||||
|
import com.tangem.common.card.EllipticCurve
|
||||||
|
import com.tangem.common.core.CardSession
|
||||||
|
import com.tangem.common.core.CardSessionRunnable
|
||||||
|
import com.tangem.common.core.CompletionCallback
|
||||||
|
import com.tangem.common.core.TangemSdkError
|
||||||
|
import com.tangem.core.error.ext.tangemError
|
||||||
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
|
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||||
|
import com.tangem.domain.visa.datasource.VisaAuthRemoteDataSource
|
||||||
|
import com.tangem.domain.visa.error.VisaActivationError
|
||||||
|
import com.tangem.domain.visa.model.TangemPayInitialCredentials
|
||||||
|
import com.tangem.domain.visa.model.VisaDataToSignByCustomerWallet
|
||||||
|
import com.tangem.domain.visa.model.VisaSignedDataByCustomerWallet
|
||||||
|
import com.tangem.domain.visa.model.sign
|
||||||
|
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||||
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
import dagger.assisted.Assisted
|
||||||
|
import dagger.assisted.AssistedFactory
|
||||||
|
import dagger.assisted.AssistedInject
|
||||||
|
import kotlinx.coroutines.CompletableDeferred
|
||||||
|
import kotlinx.coroutines.CoroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.withContext
|
||||||
|
|
||||||
|
class TangemPayGenerateAddressAndSignChallengeTask @AssistedInject constructor(
|
||||||
|
@Assisted private val coroutineScope: CoroutineScope,
|
||||||
|
private val dispatchersProvider: CoroutineDispatcherProvider,
|
||||||
|
private val visaAuthRemoteDataSource: VisaAuthRemoteDataSource,
|
||||||
|
) : CardSessionRunnable<TangemPayInitialCredentials> {
|
||||||
|
|
||||||
|
override fun run(session: CardSession, callback: CompletionCallback<TangemPayInitialCredentials>) {
|
||||||
|
coroutineScope.launch {
|
||||||
|
callback(runSuspend(session = session))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runSuspend(session: CardSession): CompletionResult<TangemPayInitialCredentials> {
|
||||||
|
val card = session.environment.card ?: return CompletionResult.Failure(TangemSdkError.MissingPreflightRead())
|
||||||
|
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 }
|
||||||
|
?: return CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError)
|
||||||
|
|
||||||
|
val derivationResult = runDerivationTask(session, wallet)
|
||||||
|
val address = when (derivationResult) {
|
||||||
|
is CompletionResult.Failure<*> -> return CompletionResult.Failure(derivationResult.error)
|
||||||
|
is CompletionResult.Success<ExtendedPublicKey> -> generateAddressFromExtendedKey(derivationResult.data)
|
||||||
|
}
|
||||||
|
|
||||||
|
val challenge = withContext(dispatchersProvider.io) {
|
||||||
|
visaAuthRemoteDataSource.getCustomerWalletAuthChallenge(address)
|
||||||
|
}.getOrElse { return CompletionResult.Failure(it.tangemError) }
|
||||||
|
|
||||||
|
val dataToSign = VisaDataToSignByCustomerWallet(hashToSign = challenge.challenge)
|
||||||
|
val approveResult = runVisaCustomerWalletApproveTask(
|
||||||
|
session = session,
|
||||||
|
cardId = card.cardId,
|
||||||
|
targetAddress = address,
|
||||||
|
dataToSign = dataToSign,
|
||||||
|
)
|
||||||
|
val signedData = when (approveResult) {
|
||||||
|
is CompletionResult.Failure<*> -> return CompletionResult.Failure(approveResult.error)
|
||||||
|
is CompletionResult.Success<VisaSignedDataByCustomerWallet> -> approveResult.data
|
||||||
|
}
|
||||||
|
|
||||||
|
val authTokens = withContext(dispatchersProvider.io) {
|
||||||
|
visaAuthRemoteDataSource.getTokenWithCustomerWallet(
|
||||||
|
sessionId = challenge.session.sessionId,
|
||||||
|
signature = signedData.signature,
|
||||||
|
nonce = signedData.dataToSign.hashToSign,
|
||||||
|
)
|
||||||
|
}.getOrNull() ?: return CompletionResult.Failure(VisaActivationError.FailedRemoteState.tangemError)
|
||||||
|
|
||||||
|
return CompletionResult.Success(
|
||||||
|
data = TangemPayInitialCredentials(
|
||||||
|
customerWalletAddress = address,
|
||||||
|
authTokens = authTokens,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runDerivationTask(
|
||||||
|
session: CardSession,
|
||||||
|
wallet: CardWallet,
|
||||||
|
): CompletionResult<ExtendedPublicKey> {
|
||||||
|
val deferred = CompletableDeferred<CompletionResult<ExtendedPublicKey>>()
|
||||||
|
val derivationTask = DeriveWalletPublicKeyTask(
|
||||||
|
walletPublicKey = wallet.publicKey,
|
||||||
|
derivationPath = VisaUtilities.customDerivationPath,
|
||||||
|
)
|
||||||
|
|
||||||
|
derivationTask.run(session = session, callback = deferred::complete)
|
||||||
|
return deferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun runVisaCustomerWalletApproveTask(
|
||||||
|
session: CardSession,
|
||||||
|
cardId: String,
|
||||||
|
targetAddress: String,
|
||||||
|
dataToSign: VisaDataToSignByCustomerWallet,
|
||||||
|
): CompletionResult<VisaSignedDataByCustomerWallet> {
|
||||||
|
val deferred = CompletableDeferred<CompletionResult<VisaSignedDataByCustomerWallet>>()
|
||||||
|
val task = VisaCustomerWalletApproveTask(
|
||||||
|
visaDataForApprove = VisaCustomerWalletApproveTask.Input(
|
||||||
|
cardId = cardId,
|
||||||
|
targetAddress = targetAddress,
|
||||||
|
hashToSign = dataToSign.hashToSign,
|
||||||
|
sign = dataToSign::sign,
|
||||||
|
),
|
||||||
|
)
|
||||||
|
task.run(session = session, callback = deferred::complete)
|
||||||
|
return deferred.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun generateAddressFromExtendedKey(extendedPublicKey: ExtendedPublicKey): String {
|
||||||
|
val derivationData = VisaUtilities.visaBlockchain.makeAddressesFromExtendedPublicKey(
|
||||||
|
extendedPublicKey = extendedPublicKey,
|
||||||
|
cachedIndex = null,
|
||||||
|
)
|
||||||
|
return derivationData.address
|
||||||
|
}
|
||||||
|
|
||||||
|
@AssistedFactory
|
||||||
|
interface Factory {
|
||||||
|
fun create(coroutineScope: CoroutineScope): TangemPayGenerateAddressAndSignChallengeTask
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -177,7 +177,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
||||||
.getOrElse { raise(it.tangemError) }
|
.getOrElse { raise(it.tangemError) }
|
||||||
|
|
||||||
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
|
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
|
||||||
return raise(VisaActivationError.WrongRemoteState.tangemError)
|
raise(VisaActivationError.WrongRemoteState.tangemError)
|
||||||
}
|
}
|
||||||
|
|
||||||
visaActivationRepository.getCardWalletAcceptanceData(
|
visaActivationRepository.getCardWalletAcceptanceData(
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,6 @@ import com.tangem.common.extensions.toHexString
|
||||||
import com.tangem.core.error.ext.tangemError
|
import com.tangem.core.error.ext.tangemError
|
||||||
import com.tangem.crypto.hdWallet.DerivationPath
|
import com.tangem.crypto.hdWallet.DerivationPath
|
||||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
|
||||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility
|
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility
|
||||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
||||||
|
|
@ -59,21 +58,7 @@ class VisaCustomerWalletApproveTask(
|
||||||
session: CardSession,
|
session: CardSession,
|
||||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||||
) {
|
) {
|
||||||
val cardDTO = CardDTO(card)
|
val derivationPath = VisaUtilities.customDerivationPath
|
||||||
|
|
||||||
val derivationStyle = cardDTO.derivationStyleProvider.getDerivationStyle() ?: run {
|
|
||||||
proceedApproveWithLegacyCard(
|
|
||||||
card = card,
|
|
||||||
session = session,
|
|
||||||
callback = callback,
|
|
||||||
)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val derivationPath = VisaUtilities.visaDefaultDerivationPath(derivationStyle) ?: run {
|
|
||||||
callback(CompletionResult.Failure(VisaActivationError.FailedToCreateAddress.tangemError))
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||||
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
||||||
|
|
|
||||||
|
|
@ -36,11 +36,6 @@ internal class BiometricUserWalletsListManager(
|
||||||
.mapLatest { it.userWallets }
|
.mapLatest { it.userWallets }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
||||||
override val savedWalletsCount: Flow<Int>
|
|
||||||
get() = state
|
|
||||||
.mapLatest { walletsCount }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
override val userWalletsSync: List<UserWallet>
|
override val userWalletsSync: List<UserWallet>
|
||||||
get() = state.value.userWallets
|
get() = state.value.userWallets
|
||||||
|
|
||||||
|
|
@ -71,6 +66,11 @@ internal class BiometricUserWalletsListManager(
|
||||||
override val walletsCount: Int
|
override val walletsCount: Int
|
||||||
get() = state.value.userWallets.size
|
get() = state.value.userWallets.size
|
||||||
|
|
||||||
|
override val savedWalletsCount: Flow<Int>
|
||||||
|
get() = state
|
||||||
|
.mapLatest { walletsCount }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
|
||||||
override suspend fun unlock(type: UnlockType): CompletionResult<UserWallet> {
|
override suspend fun unlock(type: UnlockType): CompletionResult<UserWallet> {
|
||||||
return unlockAndSetSelectedUserWallet(type)
|
return unlockAndSetSelectedUserWallet(type)
|
||||||
.mapFailure { error ->
|
.mapFailure { error ->
|
||||||
|
|
|
||||||
|
|
@ -20,11 +20,6 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
||||||
.mapLatest { listOfNotNull(it.userWallet) }
|
.mapLatest { listOfNotNull(it.userWallet) }
|
||||||
.distinctUntilChanged()
|
.distinctUntilChanged()
|
||||||
|
|
||||||
override val savedWalletsCount: Flow<Int>
|
|
||||||
get() = state
|
|
||||||
.mapLatest { walletsCount }
|
|
||||||
.distinctUntilChanged()
|
|
||||||
|
|
||||||
override val selectedUserWallet: Flow<UserWallet>
|
override val selectedUserWallet: Flow<UserWallet>
|
||||||
get() = state
|
get() = state
|
||||||
.mapLatest { it.userWallet }
|
.mapLatest { it.userWallet }
|
||||||
|
|
@ -46,6 +41,11 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
||||||
override val walletsCount: Int
|
override val walletsCount: Int
|
||||||
get() = if (hasUserWallets) 1 else 0
|
get() = if (hasUserWallets) 1 else 0
|
||||||
|
|
||||||
|
override val savedWalletsCount: Flow<Int>
|
||||||
|
get() = state
|
||||||
|
.mapLatest { walletsCount }
|
||||||
|
.distinctUntilChanged()
|
||||||
|
|
||||||
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
override suspend fun select(userWalletId: UserWalletId): CompletionResult<UserWallet> = catching {
|
||||||
state.value.userWallet
|
state.value.userWallet
|
||||||
?.takeIf { it.walletId == userWalletId }
|
?.takeIf { it.walletId == userWalletId }
|
||||||
|
|
|
||||||
|
|
@ -27,6 +27,7 @@ import com.tangem.tap.domain.userWalletList.utils.toUserWallets
|
||||||
import com.tangem.tap.domain.userWalletList.utils.updateWith
|
import com.tangem.tap.domain.userWalletList.utils.updateWith
|
||||||
import com.tangem.utils.Provider
|
import com.tangem.utils.Provider
|
||||||
import com.tangem.utils.ProviderSuspend
|
import com.tangem.utils.ProviderSuspend
|
||||||
|
import com.tangem.utils.coroutines.runSuspendCatching
|
||||||
import com.tangem.utils.extensions.indexOfFirstOrNull
|
import com.tangem.utils.extensions.indexOfFirstOrNull
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
|
|
@ -149,7 +150,7 @@ internal class DefaultUserWalletsListRepository(
|
||||||
val encryptionKey = userWallet.encryptionKey
|
val encryptionKey = userWallet.encryptionKey
|
||||||
?: raise(SetLockError.UserWalletLocked)
|
?: raise(SetLockError.UserWalletLocked)
|
||||||
|
|
||||||
runCatching {
|
runSuspendCatching {
|
||||||
userWalletEncryptionKeysRepository.save(
|
userWalletEncryptionKeysRepository.save(
|
||||||
encryptionKey = UserWalletEncryptionKey(
|
encryptionKey = UserWalletEncryptionKey(
|
||||||
walletId = userWalletId,
|
walletId = userWalletId,
|
||||||
|
|
@ -232,7 +233,7 @@ internal class DefaultUserWalletsListRepository(
|
||||||
val encryptionKey = requestPasswordRecursive(
|
val encryptionKey = requestPasswordRecursive(
|
||||||
hotWalletId = userWallet.hotWalletId,
|
hotWalletId = userWallet.hotWalletId,
|
||||||
block = { password ->
|
block = { password ->
|
||||||
runCatching {
|
runSuspendCatching {
|
||||||
userWalletEncryptionKeysRepository.getEncryptedWithPassword(userWalletId, password)
|
userWalletEncryptionKeysRepository.getEncryptedWithPassword(userWalletId, password)
|
||||||
}.onFailure {
|
}.onFailure {
|
||||||
raise(UnlockWalletError.UnableToUnlock)
|
raise(UnlockWalletError.UnableToUnlock)
|
||||||
|
|
@ -288,8 +289,7 @@ internal class DefaultUserWalletsListRepository(
|
||||||
|
|
||||||
override suspend fun unlockAllWallets(): Either<UnlockWalletError, Unit> = either {
|
override suspend fun unlockAllWallets(): Either<UnlockWalletError, Unit> = either {
|
||||||
val userWallets = userWalletsSync()
|
val userWallets = userWalletsSync()
|
||||||
val userWalletIds = userWallets.map { it.walletId }.toSet()
|
val biometricKeys = runSuspendCatching {
|
||||||
val biometricKeys = runCatching {
|
|
||||||
userWalletEncryptionKeysRepository.getAllBiometric()
|
userWalletEncryptionKeysRepository.getAllBiometric()
|
||||||
}.getOrElse {
|
}.getOrElse {
|
||||||
// TODO handle error properly [REDACTED_TASK_KEY]
|
// TODO handle error properly [REDACTED_TASK_KEY]
|
||||||
|
|
@ -309,8 +309,9 @@ internal class DefaultUserWalletsListRepository(
|
||||||
removePasswordAttempts(it)
|
removePasswordAttempts(it)
|
||||||
}
|
}
|
||||||
|
|
||||||
// if we cant unlock all wallets
|
// if we cant unlock any of the locked wallets, return error
|
||||||
if (userWalletIds.all { it in unlockedWalletsIds }.not()) {
|
// (isLocked remains `true` here because we haven't updated the wallets yet)
|
||||||
|
if (unlockedWallets.any { it.isLocked }.not()) {
|
||||||
raise(UnlockWalletError.UnableToUnlock)
|
raise(UnlockWalletError.UnableToUnlock)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -345,6 +346,12 @@ internal class DefaultUserWalletsListRepository(
|
||||||
userWalletEncryptionKeysRepository.clear()
|
userWalletEncryptionKeysRepository.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override suspend fun hasSecuredWallets(): Boolean {
|
||||||
|
val userWallets = userWalletsSync()
|
||||||
|
val unsecuredWalletIds = userWalletEncryptionKeysRepository.getAllUnsecured().map { it.walletId }.toSet()
|
||||||
|
return userWallets.any { it.walletId !in unsecuredWalletIds }
|
||||||
|
}
|
||||||
|
|
||||||
private suspend fun requestPasswordRecursive(
|
private suspend fun requestPasswordRecursive(
|
||||||
hotWalletId: HotWalletId,
|
hotWalletId: HotWalletId,
|
||||||
block: suspend (CharArray) -> UserWalletEncryptionKey?,
|
block: suspend (CharArray) -> UserWalletEncryptionKey?,
|
||||||
|
|
|
||||||
|
|
@ -5,7 +5,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
||||||
import com.tangem.tap.common.redux.AppState
|
import com.tangem.tap.common.redux.AppState
|
||||||
|
|
||||||
object DemoHelper {
|
object DemoHelper {
|
||||||
val config = DemoConfig()
|
val config = DemoConfig
|
||||||
|
|
||||||
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
|
fun isDemoCard(scanResponse: ScanResponse): Boolean = isDemoCardId(scanResponse.card.cardId)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,6 +34,7 @@ import org.rekotlin.Action
|
||||||
import org.rekotlin.Middleware
|
import org.rekotlin.Middleware
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
|
@Suppress("MemberNameEqualsClassName")
|
||||||
class DetailsMiddleware {
|
class DetailsMiddleware {
|
||||||
private val appSettingsMiddleware = AppSettingsMiddleware()
|
private val appSettingsMiddleware = AppSettingsMiddleware()
|
||||||
val detailsMiddleware: Middleware<AppState> = { _, stateProvider ->
|
val detailsMiddleware: Middleware<AppState> = { _, stateProvider ->
|
||||||
|
|
@ -66,14 +67,8 @@ class DetailsMiddleware {
|
||||||
when (action.setting) {
|
when (action.setting) {
|
||||||
AppSetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable)
|
AppSetting.SaveWallets -> toggleSaveWallets(state, enable = action.enable)
|
||||||
AppSetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable)
|
AppSetting.SaveAccessCode -> toggleSaveAccessCodes(state, enable = action.enable)
|
||||||
AppSetting.RequireAccessCode -> toggleRequireAccessCode(
|
AppSetting.RequireAccessCode -> toggleRequireAccessCode(enable = action.enable)
|
||||||
state = state,
|
AppSetting.BiometricAuthentication -> toggleBiometricsAuthentication(enable = action.enable)
|
||||||
enable = action.enable,
|
|
||||||
)
|
|
||||||
AppSetting.BiometricAuthentication -> toggleBiometricsAuthentication(
|
|
||||||
state = state,
|
|
||||||
enable = action.enable,
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
is DetailsAction.AppSettings.CheckBiometricsStatus -> {
|
is DetailsAction.AppSettings.CheckBiometricsStatus -> {
|
||||||
|
|
@ -100,7 +95,7 @@ class DetailsMiddleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleBiometricsAuthentication(state: DetailsState, enable: Boolean) {
|
private fun toggleBiometricsAuthentication(enable: Boolean) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||||
|
|
||||||
|
|
@ -110,16 +105,12 @@ class DetailsMiddleware {
|
||||||
return@launch
|
return@launch
|
||||||
}
|
}
|
||||||
|
|
||||||
toggleRequireAccessCode(
|
|
||||||
state = state,
|
|
||||||
enable = true,
|
|
||||||
)
|
|
||||||
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
setBiometricLockForAllWallets()
|
setBiometricLockForAllWallets()
|
||||||
} else {
|
} else {
|
||||||
// Remove all biometric-related data
|
// Remove all biometric-related data
|
||||||
removeAllBiometricData()
|
removeAllBiometricData()
|
||||||
|
walletsRepository.setRequireAccessCode(value = true)
|
||||||
}
|
}
|
||||||
|
|
||||||
walletsRepository.setUseBiometricAuthentication(value = enable)
|
walletsRepository.setUseBiometricAuthentication(value = enable)
|
||||||
|
|
@ -127,7 +118,7 @@ class DetailsMiddleware {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun toggleRequireAccessCode(state: DetailsState, enable: Boolean) {
|
private fun toggleRequireAccessCode(enable: Boolean) {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||||
|
|
||||||
|
|
@ -138,11 +129,8 @@ class DetailsMiddleware {
|
||||||
}
|
}
|
||||||
|
|
||||||
if (enable) {
|
if (enable) {
|
||||||
// Remove all biometric sign data
|
// Remove all saved access codes
|
||||||
removeAllBiometricSingData()
|
removeAllBiometricSingData()
|
||||||
toggleSaveAccessCodes(state, enable = false)
|
|
||||||
} else {
|
|
||||||
toggleSaveAccessCodes(state, enable = true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
walletsRepository.setRequireAccessCode(value = enable)
|
walletsRepository.setRequireAccessCode(value = enable)
|
||||||
|
|
|
||||||
|
|
@ -111,6 +111,9 @@ private fun handlePrivacyAction(action: DetailsAction.AppSettings, state: Detail
|
||||||
isHidingEnabled = action.state.isHidingEnabled,
|
isHidingEnabled = action.state.isHidingEnabled,
|
||||||
selectedAppCurrency = action.state.selectedAppCurrency,
|
selectedAppCurrency = action.state.selectedAppCurrency,
|
||||||
selectedThemeMode = action.state.selectedThemeMode,
|
selectedThemeMode = action.state.selectedThemeMode,
|
||||||
|
useBiometricAuthentication = action.state.useBiometricAuthentication,
|
||||||
|
requireAccessCode = action.state.requireAccessCode,
|
||||||
|
hasSecuredWallets = action.state.hasSecuredWallets,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
is DetailsAction.AppSettings.EnrollBiometrics,
|
is DetailsAction.AppSettings.EnrollBiometrics,
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@ data class AppSettingsState(
|
||||||
val requireAccessCode: Boolean = false,
|
val requireAccessCode: Boolean = false,
|
||||||
val useBiometricAuthentication: Boolean = false,
|
val useBiometricAuthentication: Boolean = false,
|
||||||
val needEnrollBiometrics: Boolean = false,
|
val needEnrollBiometrics: Boolean = false,
|
||||||
|
val hasSecuredWallets: Boolean = false,
|
||||||
val isHidingEnabled: Boolean = false,
|
val isHidingEnabled: Boolean = false,
|
||||||
val isInProgress: Boolean = false,
|
val isInProgress: Boolean = false,
|
||||||
val selectedAppCurrency: AppCurrency = AppCurrency.Default,
|
val selectedAppCurrency: AppCurrency = AppCurrency.Default,
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import com.tangem.domain.appcurrency.repository.AppCurrencyRepository
|
||||||
import com.tangem.domain.apptheme.model.AppThemeMode
|
import com.tangem.domain.apptheme.model.AppThemeMode
|
||||||
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
import com.tangem.domain.apptheme.repository.AppThemeModeRepository
|
||||||
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
import com.tangem.domain.balancehiding.repositories.BalanceHidingRepository
|
||||||
|
import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.settings.CanUseBiometryUseCase
|
import com.tangem.domain.settings.CanUseBiometryUseCase
|
||||||
import com.tangem.domain.settings.repositories.SettingsRepository
|
import com.tangem.domain.settings.repositories.SettingsRepository
|
||||||
import com.tangem.domain.wallets.repository.WalletsRepository
|
import com.tangem.domain.wallets.repository.WalletsRepository
|
||||||
|
|
@ -48,6 +49,7 @@ internal class AppSettingsModel @Inject constructor(
|
||||||
private val appCurrencyRepository: AppCurrencyRepository,
|
private val appCurrencyRepository: AppCurrencyRepository,
|
||||||
private val walletsRepository: WalletsRepository,
|
private val walletsRepository: WalletsRepository,
|
||||||
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
private val canUseBiometryUseCase: CanUseBiometryUseCase,
|
||||||
|
private val userWalletsListRepository: UserWalletsListRepository,
|
||||||
private val balanceHidingRepository: BalanceHidingRepository,
|
private val balanceHidingRepository: BalanceHidingRepository,
|
||||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||||
private val appThemeModeRepository: AppThemeModeRepository,
|
private val appThemeModeRepository: AppThemeModeRepository,
|
||||||
|
|
@ -114,7 +116,8 @@ internal class AppSettingsModel @Inject constructor(
|
||||||
)
|
)
|
||||||
|
|
||||||
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
if (hotWalletFeatureToggles.isHotWalletEnabled) {
|
||||||
val canUseBiometrics = !state.needEnrollBiometrics && !state.isInProgress
|
val canUseBiometrics =
|
||||||
|
!state.needEnrollBiometrics && !state.isInProgress && state.hasSecuredWallets
|
||||||
|
|
||||||
add(
|
add(
|
||||||
itemsFactory.createUseBiometricsSwitch(
|
itemsFactory.createUseBiometricsSwitch(
|
||||||
|
|
@ -126,7 +129,7 @@ internal class AppSettingsModel @Inject constructor(
|
||||||
|
|
||||||
add(
|
add(
|
||||||
itemsFactory.createRequireAccessCodeSwitch(
|
itemsFactory.createRequireAccessCodeSwitch(
|
||||||
isChecked = state.requireAccessCode,
|
isChecked = state.requireAccessCode || !state.useBiometricAuthentication,
|
||||||
isEnabled = canUseBiometrics && state.useBiometricAuthentication,
|
isEnabled = canUseBiometrics && state.useBiometricAuthentication,
|
||||||
onCheckedChange = ::onRequireAccessCodeToggled,
|
onCheckedChange = ::onRequireAccessCodeToggled,
|
||||||
),
|
),
|
||||||
|
|
@ -206,14 +209,12 @@ internal class AppSettingsModel @Inject constructor(
|
||||||
// analyticsEventHandler.send(Settings.AppSettings.BiometricAuthenticationChanged(param))
|
// analyticsEventHandler.send(Settings.AppSettings.BiometricAuthenticationChanged(param))
|
||||||
if (isChecked) {
|
if (isChecked) {
|
||||||
onSettingsToggled(AppSetting.BiometricAuthentication, enable = true)
|
onSettingsToggled(AppSetting.BiometricAuthentication, enable = true)
|
||||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
|
|
||||||
} else {
|
} else {
|
||||||
updateContentState {
|
updateContentState {
|
||||||
copy(
|
copy(
|
||||||
dialog = dialogsFactory.createDisableBiometricAuthenticationAlert(
|
dialog = dialogsFactory.createDisableBiometricAuthenticationAlert(
|
||||||
onDisable = {
|
onDisable = {
|
||||||
onSettingsToggled(AppSetting.BiometricAuthentication, enable = false)
|
onSettingsToggled(AppSetting.BiometricAuthentication, enable = false)
|
||||||
onSettingsToggled(AppSetting.RequireAccessCode, enable = true)
|
|
||||||
dismissDialog()
|
dismissDialog()
|
||||||
},
|
},
|
||||||
onDismiss = ::dismissDialog,
|
onDismiss = ::dismissDialog,
|
||||||
|
|
@ -323,6 +324,7 @@ internal class AppSettingsModel @Inject constructor(
|
||||||
isHidingEnabled = balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings,
|
isHidingEnabled = balanceHidingRepository.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||||
selectedAppCurrency = appCurrencyRepository.getSelectedAppCurrency().firstOrNull() ?: AppCurrency.Default,
|
selectedAppCurrency = appCurrencyRepository.getSelectedAppCurrency().firstOrNull() ?: AppCurrency.Default,
|
||||||
selectedThemeMode = appThemeModeRepository.getAppThemeMode().firstOrNull() ?: AppThemeMode.DEFAULT,
|
selectedThemeMode = appThemeModeRepository.getAppThemeMode().firstOrNull() ?: AppThemeMode.DEFAULT,
|
||||||
|
hasSecuredWallets = userWalletsListRepository.hasSecuredWallets(),
|
||||||
)
|
)
|
||||||
|
|
||||||
store.dispatchWithMain(DetailsAction.AppSettings.Prepare(state))
|
store.dispatchWithMain(DetailsAction.AppSettings.Prepare(state))
|
||||||
|
|
|
||||||
|
|
@ -4,9 +4,9 @@ import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||||
import com.tangem.common.keyboard.KeyboardValidator
|
import com.tangem.common.keyboard.KeyboardValidator
|
||||||
import com.tangem.core.analytics.Analytics
|
|
||||||
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
import com.tangem.core.analytics.api.AnalyticsEventHandler
|
||||||
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
|
import com.tangem.core.analytics.models.event.TechAnalyticsEvent
|
||||||
|
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||||
import com.tangem.core.decompose.ui.UiMessageSender
|
import com.tangem.core.decompose.ui.UiMessageSender
|
||||||
import com.tangem.core.ui.R
|
import com.tangem.core.ui.R
|
||||||
|
|
@ -16,14 +16,11 @@ import com.tangem.core.ui.message.BottomSheetMessage
|
||||||
import com.tangem.core.ui.message.EventMessageAction
|
import com.tangem.core.ui.message.EventMessageAction
|
||||||
import com.tangem.core.ui.message.SnackbarMessage
|
import com.tangem.core.ui.message.SnackbarMessage
|
||||||
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
import com.tangem.datasource.api.common.config.managers.ApiConfigsManager
|
||||||
import com.tangem.datasource.local.config.environment.EnvironmentConfig
|
|
||||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
|
||||||
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
|
import com.tangem.domain.appcurrency.FetchAppCurrenciesUseCase
|
||||||
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
import com.tangem.domain.balancehiding.BalanceHidingSettings
|
||||||
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
import com.tangem.domain.balancehiding.GetBalanceHidingSettingsUseCase
|
||||||
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
import com.tangem.domain.balancehiding.ListenToFlipsUseCase
|
||||||
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
import com.tangem.domain.balancehiding.UpdateBalanceHidingSettingsUseCase
|
||||||
import com.tangem.domain.common.LogConfig
|
|
||||||
import com.tangem.domain.models.wallet.UserWallet
|
import com.tangem.domain.models.wallet.UserWallet
|
||||||
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
import com.tangem.domain.notifications.GetApplicationIdUseCase
|
||||||
import com.tangem.domain.notifications.SendPushTokenUseCase
|
import com.tangem.domain.notifications.SendPushTokenUseCase
|
||||||
|
|
@ -41,9 +38,7 @@ import com.tangem.domain.wallets.usecase.GetSavedWalletsCountUseCase
|
||||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||||
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
|
import com.tangem.domain.wallets.usecase.UpdateRemoteWalletsInfoUseCase
|
||||||
import com.tangem.feature.swap.analytics.StoriesEvents
|
import com.tangem.feature.swap.analytics.StoriesEvents
|
||||||
import com.tangem.tap.common.extensions.setContext
|
import com.tangem.tap.network.exchangeServices.SellService
|
||||||
import com.tangem.tap.network.exchangeServices.ExchangeService
|
|
||||||
import com.tangem.tap.network.exchangeServices.moonpay.MoonPayService
|
|
||||||
import com.tangem.tap.proxy.AppStateHolder
|
import com.tangem.tap.proxy.AppStateHolder
|
||||||
import com.tangem.tap.routing.configurator.AppRouterConfig
|
import com.tangem.tap.routing.configurator.AppRouterConfig
|
||||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||||
|
|
@ -83,9 +78,10 @@ internal class MainViewModel @Inject constructor(
|
||||||
private val apiConfigsManager: ApiConfigsManager,
|
private val apiConfigsManager: ApiConfigsManager,
|
||||||
private val multiQuoteUpdater: MultiQuoteUpdater,
|
private val multiQuoteUpdater: MultiQuoteUpdater,
|
||||||
private val appStateHolder: AppStateHolder,
|
private val appStateHolder: AppStateHolder,
|
||||||
private val environmentConfigStorage: EnvironmentConfigStorage,
|
|
||||||
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
private val getSelectedWalletUseCase: GetSelectedWalletUseCase,
|
||||||
private val appRouterConfig: AppRouterConfig,
|
private val appRouterConfig: AppRouterConfig,
|
||||||
|
private val sellService: SellService,
|
||||||
|
private val trackingContextProxy: TrackingContextProxy,
|
||||||
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
getBalanceHidingSettingsUseCase: GetBalanceHidingSettingsUseCase,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
|
|
||||||
|
|
@ -181,7 +177,7 @@ internal class MainViewModel @Inject constructor(
|
||||||
.mapLeft { emptyFlow<UserWallet>() }
|
.mapLeft { emptyFlow<UserWallet>() }
|
||||||
.onRight { wallet ->
|
.onRight { wallet ->
|
||||||
wallet.distinctUntilChanged()
|
wallet.distinctUntilChanged()
|
||||||
.onEach { Analytics.setContext(it) }
|
.onEach { trackingContextProxy.setContext(it) }
|
||||||
.flowOn(dispatchers.io)
|
.flowOn(dispatchers.io)
|
||||||
.launchIn(viewModelScope)
|
.launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
@ -195,22 +191,12 @@ internal class MainViewModel @Inject constructor(
|
||||||
|
|
||||||
private fun initializeOffRamp() {
|
private fun initializeOffRamp() {
|
||||||
viewModelScope.launch {
|
viewModelScope.launch {
|
||||||
val sellService = makeSellExchangeService(environmentConfig = environmentConfigStorage.getConfigSync())
|
|
||||||
appStateHolder.sellService = sellService
|
appStateHolder.sellService = sellService
|
||||||
|
|
||||||
sellService.update()
|
sellService.update()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun makeSellExchangeService(environmentConfig: EnvironmentConfig): ExchangeService {
|
|
||||||
return MoonPayService(
|
|
||||||
apiKey = environmentConfig.moonPayApiKey,
|
|
||||||
secretKey = environmentConfig.moonPayApiSecretKey,
|
|
||||||
isLogEnabled = LogConfig.network.moonPayService,
|
|
||||||
userWalletProvider = { getSelectedWalletUseCase.sync().getOrNull() },
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun observeFlips() {
|
private fun observeFlips() {
|
||||||
listenToFlipsUseCase().launchIn(viewModelScope)
|
listenToFlipsUseCase().launchIn(viewModelScope)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@ import com.tangem.tap.store
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import org.rekotlin.Middleware
|
import org.rekotlin.Middleware
|
||||||
|
|
||||||
|
@Suppress("MemberNameEqualsClassName")
|
||||||
class BackupMiddleware {
|
class BackupMiddleware {
|
||||||
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
|
val backupMiddleware: Middleware<AppState> = { dispatch, state ->
|
||||||
{ next ->
|
{ next ->
|
||||||
|
|
|
||||||
|
|
@ -77,9 +77,13 @@ internal class WelcomeModel @Inject constructor(
|
||||||
warning = warning,
|
warning = warning,
|
||||||
error = state.error
|
error = state.error
|
||||||
?.takeIf { !it.silent && warning == null }
|
?.takeIf { !it.silent && warning == null }
|
||||||
?.let { e ->
|
?.let { error ->
|
||||||
e.messageResId?.let { TextReference.Res(it) }
|
val messageResId = error.messageResId
|
||||||
?: TextReference.Str(e.customMessage)
|
if (messageResId != null) {
|
||||||
|
TextReference.Res(messageResId)
|
||||||
|
} else {
|
||||||
|
TextReference.Str(error.customMessage)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -100,7 +100,10 @@ internal class WelcomeMiddleware {
|
||||||
val currency = ParamCardCurrencyConverter().convert(
|
val currency = ParamCardCurrencyConverter().convert(
|
||||||
value = scanResponse.cardTypesResolver,
|
value = scanResponse.cardTypesResolver,
|
||||||
)
|
)
|
||||||
Analytics.addContext(scanResponse)
|
|
||||||
|
val trackingContextProxy = store.inject(DaggerGraphState::trackingContextProxy)
|
||||||
|
trackingContextProxy.addContext(scanResponse)
|
||||||
|
|
||||||
if (currency != null) {
|
if (currency != null) {
|
||||||
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
val userWalletsListManager = store.inject(DaggerGraphState::generalUserWalletsListManager)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,20 @@
|
||||||
|
package com.tangem.tap.network.auth
|
||||||
|
|
||||||
|
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||||
|
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||||
|
|
||||||
|
internal class DefaultP2PEthPoolAuthProvider(
|
||||||
|
private val environmentConfigStorage: EnvironmentConfigStorage,
|
||||||
|
) : P2PEthPoolAuthProvider {
|
||||||
|
|
||||||
|
override fun getApiKey(): String {
|
||||||
|
// val keys = environmentConfigStorage.getConfigSync().p2pApiKey
|
||||||
|
// ?: error("No P2P api keys provided")
|
||||||
|
//
|
||||||
|
// return keys.mainnet
|
||||||
|
|
||||||
|
environmentConfigStorage
|
||||||
|
|
||||||
|
return "TODO restore p2p config call after release 5.30"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -6,10 +6,12 @@ import com.tangem.domain.common.wallets.UserWalletsListRepository
|
||||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||||
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
import com.tangem.features.hotwallet.HotWalletFeatureToggles
|
||||||
import com.tangem.lib.auth.ExpressAuthProvider
|
import com.tangem.lib.auth.ExpressAuthProvider
|
||||||
|
import com.tangem.lib.auth.P2PEthPoolAuthProvider
|
||||||
import com.tangem.lib.auth.StakeKitAuthProvider
|
import com.tangem.lib.auth.StakeKitAuthProvider
|
||||||
import com.tangem.tap.network.auth.DefaultAppVersionProvider
|
import com.tangem.tap.network.auth.DefaultAppVersionProvider
|
||||||
import com.tangem.tap.network.auth.DefaultAuthProvider
|
import com.tangem.tap.network.auth.DefaultAuthProvider
|
||||||
import com.tangem.tap.network.auth.DefaultExpressAuthProvider
|
import com.tangem.tap.network.auth.DefaultExpressAuthProvider
|
||||||
|
import com.tangem.tap.network.auth.DefaultP2PEthPoolAuthProvider
|
||||||
import com.tangem.tap.network.auth.DefaultStakeKitAuthProvider
|
import com.tangem.tap.network.auth.DefaultStakeKitAuthProvider
|
||||||
import com.tangem.utils.version.AppVersionProvider
|
import com.tangem.utils.version.AppVersionProvider
|
||||||
import dagger.Module
|
import dagger.Module
|
||||||
|
|
@ -50,6 +52,12 @@ internal class AuthModule {
|
||||||
return DefaultStakeKitAuthProvider(environmentConfigStorage)
|
return DefaultStakeKitAuthProvider(environmentConfigStorage)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Provides
|
||||||
|
@Singleton
|
||||||
|
fun provideP2PEthPoolAuthProvider(environmentConfigStorage: EnvironmentConfigStorage): P2PEthPoolAuthProvider {
|
||||||
|
return DefaultP2PEthPoolAuthProvider(environmentConfigStorage)
|
||||||
|
}
|
||||||
|
|
||||||
@Provides
|
@Provides
|
||||||
@Singleton
|
@Singleton
|
||||||
fun provideAppVersionProvider(): AppVersionProvider {
|
fun provideAppVersionProvider(): AppVersionProvider {
|
||||||
|
|
|
||||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue