Updated on 2026-08-14
This commit is contained in:
commit
9618842f99
1534 changed files with 48166 additions and 10691 deletions
40
LICENSE
40
LICENSE
|
|
@ -2,17 +2,37 @@ Tangem Proprietary and Confidential
|
|||
Copyright (c) 2025 Tangem AG. All rights reserved.
|
||||
================================================================================
|
||||
NOTICE: THIS IS NOT AN OPEN SOURCE LICENSE.
|
||||
This software, including all source code, documentation, and accompanying files (the "Software") stored in this repository, is the proprietary and confidential information of Tangem AG ("Tangem"). The publication of this Software on a public platform does not grant any license, express or implied.
|
||||
|
||||
This software, including all source code, documentation, and accompanying files (the "Software") stored in this repository, is the proprietary and confidential information of Tangem AG ("Tangem").
|
||||
The publication of this Software on a public platform does not grant any license, express or implied, except as explicitly stated in Section 2 below.
|
||||
|
||||
1. Definitions.
|
||||
"Software" refers to all contents of this repository.
|
||||
"Protocol" refers to the proprietary communication protocol, including the sequence of, data structures, and cryptographic methods used to interact with Tangem hardware.
|
||||
"SDKs" refer to the official Software Development Kits (e.g., for Android, iOS, React Native) provided by Tangem in separate repositories and under their own distinct licenses.
|
||||
2. Strict Prohibition of Use. Any use of the Software, in whole or in part, is strictly prohibited without the express prior written consent of Tangem. This prohibition includes, but is not limited to, the following actions for any purpose, whether commercial or non-commercial:
|
||||
- Copying, modifying, or merging the Software.
|
||||
- Publishing, distributing, or sublicensing the Software.
|
||||
|
||||
2. Limited License for Personal Use and Contributions.
|
||||
Notwithstanding the proprietary nature of the Software, Tangem grants you a personal, non-exclusive, non-transferable, revocable license to:
|
||||
- Clone, fork, and modify the Software solely for personal, non-commercial purposes (e.g., for security auditing, verification, or building the application for installation on your personal device).
|
||||
- Use the Software to interact with Tangem hardware for personal needs.
|
||||
- Submit proposals for changes (Pull Requests) to improve the Software.
|
||||
|
||||
3. Strict Prohibition of Commercial Use and Distribution.
|
||||
Except for the rights expressly granted in Section 2, any use of the Software, in whole or in part, remains strictly prohibited without the express prior written consent of Tangem.
|
||||
This prohibition applies to any commercial purpose and includes, but is not limited to:
|
||||
- Publishing, distributing, or sublicensing the Software or its derivatives.
|
||||
- Selling copies of the Software.
|
||||
- Creating derivative works based on the Software.
|
||||
3. Protocol Usage. Reverse-engineering, analyzing, decompiling, or attempting to recreate the Protocol is strictly prohibited. The only authorized method for third-party applications to interact with Tangem hardware is by using the official Tangem SDKs. The SDKs are governed by their own license terms (e.g., MIT License), which permit their use in third-party applications. This Software is provided for transparency and reference purposes only.
|
||||
4. No Implied Rights. The act of Tangem publishing the Software to a public repository does not grant users any implied rights or licenses to use, modify, or distribute the Software. All rights not expressly granted by Tangem in a separate written agreement are reserved.
|
||||
5. Legal Action. Unauthorized use, reproduction, or distribution of the Software or the Protocol may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under applicable law.
|
||||
For inquiries about licensing or permissions to use the Software or the Protocol, please contact Tangem AG at: legal@tangem.com.
|
||||
- Creating a competing product or service based on the Software.
|
||||
- Integrating the Software or the Protocol into third-party commercial applications or services.
|
||||
|
||||
4. Protocol Usage.
|
||||
Reverse-engineering, decompiling, or attempting to recreate the Protocol for use in unauthorized applications is strictly prohibited.
|
||||
Any interaction with Tangem hardware in commercial or distributed applications requires a separate commercial agreement with Tangem.
|
||||
This Software is provided for transparency and reference purposes.
|
||||
|
||||
5. No Implied Rights.
|
||||
The act of Tangem publishing the Software to a public repository does not grant users any implied rights or licenses to use, modify, or distribute the Software beyond the limited permissions described in Section 2.
|
||||
All rights not expressly granted by Tangem in a separate written agreement are reserved.
|
||||
|
||||
6. Legal Action.
|
||||
Unauthorized use, reproduction, or distribution of the Software or the Protocol may result in severe civil and criminal penalties, and will be prosecuted to the maximum extent possible under applicable law.
|
||||
For inquiries about licensing or permissions to use the Software or the Protocol, please contact Tangem AG at: legal@tangem.com.
|
||||
|
|
@ -113,12 +113,14 @@ dependencies {
|
|||
implementation(projects.domain.legacy)
|
||||
implementation(projects.libs.blockchainSdk)
|
||||
implementation(projects.domain.account)
|
||||
implementation(projects.domain.account.status)
|
||||
implementation(projects.domain.models)
|
||||
implementation(projects.domain.core)
|
||||
api(projects.domain.common)
|
||||
implementation(projects.domain.card)
|
||||
implementation(projects.domain.demo)
|
||||
implementation(projects.domain.demo.models)
|
||||
implementation(projects.domain.express)
|
||||
implementation(projects.domain.wallets)
|
||||
implementation(projects.domain.wallets.models)
|
||||
implementation(projects.domain.settings)
|
||||
|
|
@ -160,6 +162,8 @@ dependencies {
|
|||
implementation(projects.domain.walletManager.models)
|
||||
implementation(projects.domain.yieldSupply)
|
||||
implementation(projects.domain.blockaid)
|
||||
implementation(projects.domain.hotWallet)
|
||||
implementation(projects.domain.news)
|
||||
|
||||
implementation(projects.common)
|
||||
implementation(projects.common.routing)
|
||||
|
|
@ -167,6 +171,7 @@ dependencies {
|
|||
implementation(projects.core.analytics)
|
||||
implementation(projects.core.analytics.models)
|
||||
implementation(projects.core.navigation)
|
||||
implementation(projects.core.abTests)
|
||||
implementation(projects.core.configToggles)
|
||||
implementation(projects.core.res)
|
||||
implementation(projects.core.ui)
|
||||
|
|
@ -209,6 +214,7 @@ dependencies {
|
|||
implementation(projects.data.swap)
|
||||
implementation(projects.data.walletManager)
|
||||
implementation(projects.data.yieldSupply)
|
||||
implementation(projects.data.hotWallet)
|
||||
|
||||
/** Features */
|
||||
implementation(projects.features.referral.impl)
|
||||
|
|
@ -245,6 +251,8 @@ dependencies {
|
|||
implementation(projects.features.walletSettings.impl)
|
||||
implementation(projects.features.markets.api)
|
||||
implementation(projects.features.markets.impl)
|
||||
implementation(projects.features.feed.api)
|
||||
implementation(projects.features.feed.impl)
|
||||
implementation(projects.features.onramp.api)
|
||||
implementation(projects.features.onramp.impl)
|
||||
implementation(projects.features.onboardingV2.api)
|
||||
|
|
|
|||
|
|
@ -63,7 +63,6 @@ abstract class BaseTestCase : TestCase(
|
|||
private val apiEnvironmentRule = ApiEnvironmentRule()
|
||||
private val permissionRule = GrantPermissionRule.grant(
|
||||
Manifest.permission.POST_NOTIFICATIONS,
|
||||
Manifest.permission.CAMERA,
|
||||
)
|
||||
|
||||
/**
|
||||
|
|
|
|||
|
|
@ -4,8 +4,32 @@ object TestConstants {
|
|||
const val TOTAL_BALANCE = "$3,299.18"
|
||||
|
||||
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 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_LONG = 30_000L
|
||||
|
|
@ -14,4 +38,7 @@ object TestConstants {
|
|||
|
||||
const val ALLURE_LABEL_NAME = "Owner"
|
||||
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(
|
||||
direction = SwipeDirection.DOWN,
|
||||
startHeightRatio = 0.2f,
|
||||
endHeightRatio = 0.8f,
|
||||
steps = 1000
|
||||
steps = steps
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -115,4 +115,28 @@ fun BaseTestCase.checkMultiCurrencyMainScreen(
|
|||
step("Assert 'Organize tokens' button is displayed") {
|
||||
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.title
|
||||
import com.tangem.screens.ScanWarningDialogPageObject
|
||||
import com.tangem.screens.onActionIsUnavailableDialog
|
||||
import com.tangem.screens.onFailedTransactionDialog
|
||||
import io.qameta.allure.kotlin.Allure.step
|
||||
|
||||
|
|
@ -63,4 +64,16 @@ fun checkAlreadyUsedWalletDialog() {
|
|||
step("Assert 'Request support' button is displayed") {
|
||||
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() }
|
||||
}
|
||||
}
|
||||
141
app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt
Normal file
141
app/src/androidTest/kotlin/com/tangem/scenarios/SendScenarios.kt
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
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' with description '$description' is displayed") {
|
||||
onSendAddressScreen {
|
||||
recentAddressItem(recipientAddress = address, description = description).assertIsDisplayed()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
||||
val tokenAmountField: KNode = child {
|
||||
hasParent(withTestTag(BuyTokenDetailsScreenTestTags.TOKEN_AMOUNT))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val recommendedTitle: KNode = child {
|
||||
hasText(getResourceString(OnrampImplR.string.onramp_recommended_title))
|
||||
}
|
||||
|
||||
val bestRateIcon: KNode = child {
|
||||
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_ICON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val bestRateTitle: KNode = child {
|
||||
hasTestTag(OnrampOffersBlockTestTags.BEST_RATE_TITLE)
|
||||
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)
|
||||
|
|
@ -44,7 +44,7 @@ class DetailsPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
}
|
||||
val contactSupportButton: KNode = child {
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
hasText(getResourceString(R.string.details_row_title_contact_to_support))
|
||||
hasText(getResourceString(R.string.common_contact_support))
|
||||
}
|
||||
val toSButton: KNode = child {
|
||||
hasTestTag(DetailsScreenTestTags.SCREEN_ITEM)
|
||||
|
|
|
|||
|
|
@ -17,6 +17,14 @@ class DialogPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
hasTestTag(BaseDialogTestTags.CONTAINER)
|
||||
}
|
||||
|
||||
val title: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TITLE)
|
||||
}
|
||||
|
||||
val text: KNode = child {
|
||||
hasTestTag(BaseDialogTestTags.TEXT)
|
||||
}
|
||||
|
||||
val cancelButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
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 com.tangem.common.BaseTestCase
|
||||
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.Companion.onComposeScreen
|
||||
import io.github.kakaocup.compose.node.element.KNode
|
||||
import io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
|
||||
class WalletConnectScanQrPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<WalletConnectScanQrPageObject>(semanticsProvider = semanticsProvider) {
|
||||
class SellPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SellPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val pasteFromClipboardButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.wallet_connect_paste_from_clipboard))
|
||||
val title: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(R.string.common_sell))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
internal fun BaseTestCase.onWalletConnectScanQrScreen(function: WalletConnectScanQrPageObject.() -> Unit) =
|
||||
internal fun BaseTestCase.onSellScreen(function: SellPageObject.() -> Unit) =
|
||||
onComposeScreen(composeTestRule, function)
|
||||
|
|
@ -3,24 +3,164 @@ package com.tangem.screens
|
|||
import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.core.ui.test.BaseButtonTestTags
|
||||
import com.tangem.core.ui.test.FooterTestTags
|
||||
import com.tangem.core.ui.test.SendAddressScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.features.send.v2.impl.R
|
||||
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.core.ui.R as CoreUiR
|
||||
|
||||
class SendAddressPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
||||
ComposeScreen<SendAddressPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val container: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.CONTAINER)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val topAppBarTitle: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
hasText(getResourceString(CoreUiR.string.common_address))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val addressTextFieldTitle: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD_TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val addressTextField: KNode = child {
|
||||
hasTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val addressTextFieldHint: KNode = child {
|
||||
hasParent(withTestTag(SendAddressScreenTestTags.ADDRESS_TEXT_FIELD))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun recipientNetworkCaution(network: String): KNode = child {
|
||||
val expectedText = getResourceString(R.string.send_recipient_address_footer, network)
|
||||
.replace("**", "")
|
||||
hasText(expectedText, substring = true)
|
||||
hasTestTag(FooterTestTags.FOOTER_TEXT)
|
||||
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,
|
||||
description: String? = null
|
||||
): 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 (description != null) {
|
||||
hasAnyDescendant(withText(description, substring = 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 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 {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(R.string.common_next)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val continueButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_next))
|
||||
hasText(getResourceString(R.string.common_continue))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,36 +2,60 @@ 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.NotificationTestTags
|
||||
import com.tangem.core.ui.test.SendConfirmScreenTestTags
|
||||
import com.tangem.core.ui.test.TopAppBarTestTags
|
||||
import com.tangem.core.ui.test.*
|
||||
import com.tangem.wallet.R
|
||||
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.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) :
|
||||
ComposeScreen<SendConfirmPageObject>(semanticsProvider = semanticsProvider) {
|
||||
|
||||
val title: KNode = child {
|
||||
val appBarTitle: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.TITLE)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val sendButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(R.string.common_send))
|
||||
val closeButton: KNode = child {
|
||||
hasTestTag(TopAppBarTestTags.CLOSE_BUTTON)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val sendButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.BUTTON)
|
||||
hasAnyDescendant(withText(getResourceString(R.string.common_send)))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val minimumSendAmountErrorTitle: KNode = child {
|
||||
val primaryAmount: KNode = child {
|
||||
hasTestTag(BaseAmountBlockTestTags.PRIMARY_AMOUNT)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
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)
|
||||
hasText(getResourceString(CommonUiR.string.send_notification_invalid_amount_title))
|
||||
hasText(title)
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
|
|
@ -40,34 +64,76 @@ class SendConfirmPageObject(semanticsProvider: SemanticsNodeInteractionsProvider
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun minimumSendAmountErrorIcon(amount: String): KNode = child {
|
||||
hasAnySibling(
|
||||
withText(
|
||||
getResourceString(
|
||||
CommonUiR.string.send_notification_invalid_minimum_amount_text,
|
||||
amount,
|
||||
amount,
|
||||
)
|
||||
)
|
||||
)
|
||||
fun sendWarningIcon(message: String): KNode = child {
|
||||
hasTestTag(NotificationTestTags.ICON)
|
||||
hasAnySibling(withText(message))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
fun minimumSendAmountErrorMessage(
|
||||
amount: String,
|
||||
fun sendWarningMessage(
|
||||
message: String,
|
||||
): KNode = child {
|
||||
hasTestTag(NotificationTestTags.MESSAGE)
|
||||
hasText(
|
||||
getResourceString(
|
||||
CommonUiR.string.send_notification_invalid_minimum_amount_text,
|
||||
amount,
|
||||
amount,
|
||||
)
|
||||
)
|
||||
hasText(message)
|
||||
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) =
|
||||
|
|
|
|||
|
|
@ -38,6 +38,16 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
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 {
|
||||
hasTestTag(SendScreenTestTags.TOKEN_NAME)
|
||||
useUnmergedTree = true
|
||||
|
|
@ -64,6 +74,12 @@ class SendPageObject(semanticsProvider: SemanticsNodeInteractionsProvider) :
|
|||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
val continueButton: KNode = child {
|
||||
hasTestTag(BaseButtonTestTags.TEXT)
|
||||
hasText(getResourceString(SendR.string.common_continue))
|
||||
useUnmergedTree = true
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
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)
|
||||
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)
|
||||
hasText(getResourceString(R.string.common_swap))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val sellButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
fun sellButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_sell))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val buyButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
fun buyButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_buy))
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalTestApi::class)
|
||||
val sendButton: LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
fun sendButton(): LazyListItemNode = horizontalActionChips.childWith<LazyListItemNode> {
|
||||
hasTestTag(BaseActionButtonsBlockTestTags.ACTION_BUTTON)
|
||||
hasText(getResourceString(R.string.common_send))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,14 +2,23 @@ package com.tangem.tests
|
|||
|
||||
import com.tangem.common.BaseTestCase
|
||||
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.pullToRefresh
|
||||
import com.tangem.common.ui.R
|
||||
import com.tangem.common.utils.resetWireMockScenarioState
|
||||
import com.tangem.common.utils.setWireMockScenarioState
|
||||
import com.tangem.scenarios.checkSendWarning
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.openSendScreen
|
||||
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 io.github.kakaocup.kakao.common.utilities.getResourceString
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
|
@ -26,28 +35,18 @@ class BlockchainTest : BaseTestCase() {
|
|||
val validSendAmount = "10"
|
||||
val minAmount = "ADA 1.00"
|
||||
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(
|
||||
additionalAfterSection = {
|
||||
resetWireMockScenarioState(scenarioName)
|
||||
resetWireMockScenarioState(USER_TOKENS_API_SCENARIO)
|
||||
}
|
||||
).run {
|
||||
step("Set WireMock scenario: '$scenarioName' to state: '$scenarioState'") {
|
||||
setWireMockScenarioState(scenarioName = scenarioName, 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() }
|
||||
step("Open 'Send Screen' with token: $tokenName") {
|
||||
openSendScreen(tokenName)
|
||||
}
|
||||
step("Type '$errorSendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
|
|
@ -64,47 +63,30 @@ class BlockchainTest : BaseTestCase() {
|
|||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Invalid amount' error title is displayed") {
|
||||
onSendConfirmScreen { minimumSendAmountErrorTitle.assertIsDisplayed() }
|
||||
step("Assert 'Invalid amount warning' is displayed") {
|
||||
checkSendWarning(
|
||||
title = invalidAmountTitle,
|
||||
message = invalidAmountMessage,
|
||||
)
|
||||
}
|
||||
step("Assert 'Invalid amount' error icon is displayed") {
|
||||
onSendConfirmScreen { minimumSendAmountErrorIcon(minAmount).assertIsDisplayed() }
|
||||
}
|
||||
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("Click on 'Amount' field") {
|
||||
onSendConfirmScreen { primaryAmount.clickWithAssertion() }
|
||||
}
|
||||
step("Type '$validSendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
amountInputTextField.performClick()
|
||||
amountInputTextField.performTextReplacement(errorSendAmount)
|
||||
amountInputTextField.performTextReplacement(validSendAmount)
|
||||
}
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendScreen { nextButton.clickWithAssertion() }
|
||||
step("Click on 'Continue' button") {
|
||||
onSendScreen { continueButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert address text field is displayed") {
|
||||
onSendAddressScreen { addressTextField.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Next' button") {
|
||||
onSendAddressScreen { nextButton.clickWithAssertion() }
|
||||
}
|
||||
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() }
|
||||
step("Assert 'Invalid amount warning' is not displayed") {
|
||||
checkSendWarning(
|
||||
title = invalidAmountTitle,
|
||||
message = invalidAmountMessage,
|
||||
isDisplayed = false
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -136,10 +118,16 @@ class BlockchainTest : BaseTestCase() {
|
|||
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||
}
|
||||
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'") {
|
||||
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesErrorState)
|
||||
setWireMockScenarioState(
|
||||
scenarioName = rippleAccountLinesScenarioName,
|
||||
state = rippleAccountLinesErrorState
|
||||
)
|
||||
}
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
|
|
@ -169,10 +157,16 @@ class BlockchainTest : BaseTestCase() {
|
|||
setWireMockScenarioState(scenarioName = userTokensScenarioName, state = userTokensScenarioState)
|
||||
}
|
||||
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'") {
|
||||
setWireMockScenarioState(scenarioName = rippleAccountLinesScenarioName, state = rippleAccountLinesStartedState)
|
||||
setWireMockScenarioState(
|
||||
scenarioName = rippleAccountLinesScenarioName,
|
||||
state = rippleAccountLinesStartedState
|
||||
)
|
||||
}
|
||||
step("Pull to refresh") {
|
||||
pullToRefresh()
|
||||
|
|
|
|||
|
|
@ -70,7 +70,7 @@ class BuyTokenTest : BaseTestCase() {
|
|||
val otherCurrenciesTitle = "Other currencies"
|
||||
val australianDollar = "AUD"
|
||||
val fiatAmount = "1"
|
||||
val tokenAmount = "~488.24938338 POL"
|
||||
val tokenAmount = "POL 488.24938338"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
|
|
@ -98,10 +98,8 @@ class BuyTokenTest : BaseTestCase() {
|
|||
step("Write fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||
}
|
||||
step("Assert token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen {
|
||||
tokenAmountField.assertTextContains(tokenAmount)
|
||||
}
|
||||
step("Assert offer token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Fiat currency icon is displayed") {
|
||||
onBuyTokenDetailsScreen { fiatCurrencyIcon.assertIsDisplayed() }
|
||||
|
|
@ -129,10 +127,8 @@ class BuyTokenTest : BaseTestCase() {
|
|||
fiatAmountTextField.assertTextContains(australianDollar + fiatAmount)
|
||||
}
|
||||
}
|
||||
step("Assert token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen {
|
||||
tokenAmountField.assertTextContains(tokenAmount)
|
||||
}
|
||||
step("Assert offer token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -145,7 +141,7 @@ class BuyTokenTest : BaseTestCase() {
|
|||
val tokenTitle = "Polygon"
|
||||
val euro = "EUR"
|
||||
val fiatAmount = "1"
|
||||
val tokenAmount = "~488.24938338 POL"
|
||||
val tokenAmount = "POL 488.24938338"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
|
|
@ -182,18 +178,12 @@ class BuyTokenTest : BaseTestCase() {
|
|||
step("Assert fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.assertTextContains(euro + fiatAmount) }
|
||||
}
|
||||
step("Assert token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Click on 'Continue' button") {
|
||||
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
|
||||
step("Assert offer token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Assert 'Recommended' title is displayed") {
|
||||
onBuyTokenDetailsScreen { recommendedTitle.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Best rate' icon is displayed") {
|
||||
onBuyTokenDetailsScreen { bestRateIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Best rate' title is displayed") {
|
||||
onBuyTokenDetailsScreen { bestRateTitle.assertIsDisplayed() }
|
||||
}
|
||||
|
|
@ -316,7 +306,7 @@ class BuyTokenTest : BaseTestCase() {
|
|||
val paymentMethod = "Invoice Revolut Pay"
|
||||
val fiatAmount = "1"
|
||||
val providerNameMercuryo = "Mercuryo"
|
||||
val tokenAmount = "~488.24938338 POL"
|
||||
val tokenAmount = "POL 488.24938338"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
|
|
@ -344,11 +334,8 @@ class BuyTokenTest : BaseTestCase() {
|
|||
step("Write fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||
}
|
||||
step("Assert token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Click on 'Continue' button") {
|
||||
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
|
||||
step("Assert offer token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Assert 'Provider block' is displayed") {
|
||||
onBuyTokenDetailsScreen { providerName.assertIsDisplayed() }
|
||||
|
|
@ -365,9 +352,6 @@ class BuyTokenTest : BaseTestCase() {
|
|||
step("Assert 'Provider' bottom sheet subtitle is displayed") {
|
||||
onSelectProviderBottomSheet { subtitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Best rate' icon is displayed") {
|
||||
onSelectProviderBottomSheet { bestRateIcon.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Best rate' title is displayed") {
|
||||
onSelectProviderBottomSheet { bestRateTitle.assertIsDisplayed() }
|
||||
}
|
||||
|
|
@ -406,7 +390,7 @@ class BuyTokenTest : BaseTestCase() {
|
|||
val invoiceRevolutPay = "Invoice Revolut Pay"
|
||||
val sepa = "Sepa"
|
||||
val fiatAmount = "1"
|
||||
val tokenAmount = "~488.24938338 POL"
|
||||
val tokenAmount = "POL 488.24938338"
|
||||
val scenarioName = "payment_methods"
|
||||
|
||||
step("Reset WireMock scenario '$scenarioName'") {
|
||||
|
|
@ -434,11 +418,8 @@ class BuyTokenTest : BaseTestCase() {
|
|||
step("Write fiat amount = '$fiatAmount'") {
|
||||
onBuyTokenDetailsScreen { fiatAmountTextField.performTextInput(fiatAmount) }
|
||||
}
|
||||
step("Assert token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { tokenAmountField.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Click on 'Continue' button") {
|
||||
onBuyTokenDetailsScreen { continueButton.clickWithAssertion() }
|
||||
step("Assert offer token amount = '$tokenAmount'") {
|
||||
onBuyTokenDetailsScreen { offerTokenAmount.assertTextContains(tokenAmount) }
|
||||
}
|
||||
step("Assert 'Provider block' is displayed") {
|
||||
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.store
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
import io.qameta.allure.kotlin.Allure
|
||||
import io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
import org.junit.Test
|
||||
|
|
@ -37,7 +36,11 @@ class FeedbackTest : BaseTestCase() {
|
|||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Click 'More' button on TopBar") {
|
||||
waitForIdle()
|
||||
onTopBar { moreButton.clickWithAssertion() }
|
||||
}
|
||||
step("Click 'Contact support' button") {
|
||||
|
|
@ -73,7 +76,7 @@ class FeedbackTest : BaseTestCase() {
|
|||
onMainScreen { tokenWithTitleAndAddress(tokenName).clickWithAssertion() }
|
||||
}
|
||||
step("Click 'Send' button") {
|
||||
onTokenDetailsScreen { sendButton.performClick() }
|
||||
onTokenDetailsScreen { sendButton().performClick() }
|
||||
}
|
||||
step("Type '$sendAmount' in input text field") {
|
||||
onSendScreen {
|
||||
|
|
@ -130,7 +133,7 @@ class FeedbackTest : BaseTestCase() {
|
|||
MockProvider.resetEmulateError()
|
||||
}
|
||||
).run {
|
||||
Allure.step("Click on 'Accept' button") {
|
||||
step("Click on 'Accept' button") {
|
||||
onDisclaimerScreen { acceptButton.clickWithAssertion() }
|
||||
}
|
||||
step("Set scanning error") {
|
||||
|
|
|
|||
|
|
@ -47,7 +47,7 @@ class SwapTokenTest : BaseTestCase() {
|
|||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
onTokenDetailsScreen { swapButton().performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
|
|
@ -131,7 +131,7 @@ class SwapTokenTest : BaseTestCase() {
|
|||
disableMobileData()
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
onTokenDetailsScreen { swapButton().performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
|
|
@ -175,7 +175,7 @@ class SwapTokenTest : BaseTestCase() {
|
|||
onTokenDetailsScreen { title.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Swap' button") {
|
||||
onTokenDetailsScreen { swapButton.performClick() }
|
||||
onTokenDetailsScreen { swapButton().performClick() }
|
||||
}
|
||||
step("Close 'Stories' screen") {
|
||||
onSwapStoriesScreen { closeButton.clickWithAssertion() }
|
||||
|
|
|
|||
|
|
@ -1,5 +1,6 @@
|
|||
package com.tangem.tests
|
||||
|
||||
import android.Manifest
|
||||
import com.tangem.common.BaseTestCase
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
|
|
@ -8,7 +9,7 @@ import com.tangem.common.utils.setClipboardText
|
|||
import com.tangem.scenarios.*
|
||||
import com.tangem.screens.onWalletConnectBottomSheet
|
||||
import com.tangem.screens.onWalletConnectDetailsBottomSheet
|
||||
import com.tangem.screens.onWalletConnectScanQrScreen
|
||||
import com.tangem.screens.onScanQrScreen
|
||||
import com.tangem.screens.onWalletConnectScreen
|
||||
import com.tangem.wallet.BuildConfig
|
||||
import dagger.hilt.android.testing.HiltAndroidTest
|
||||
|
|
@ -196,8 +197,14 @@ class WalletConnectTest : BaseTestCase() {
|
|||
val dAppName = "React App"
|
||||
val context = device.context
|
||||
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") {
|
||||
setClipboardText(context, deepLinkUri)
|
||||
}
|
||||
|
|
@ -214,7 +221,7 @@ class WalletConnectTest : BaseTestCase() {
|
|||
onWalletConnectScreen { newConnectionButton.performClick() }
|
||||
}
|
||||
step("CLick 'Paste from clipboard' button") {
|
||||
onWalletConnectScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
|
||||
onScanQrScreen { pasteFromClipboardButton.clickWithAssertion() }
|
||||
}
|
||||
step("Check 'Wallet Connect' bottom sheet") {
|
||||
waitForIdle()
|
||||
|
|
|
|||
|
|
@ -2,14 +2,19 @@ package com.tangem.tests.actionButtons
|
|||
|
||||
import androidx.compose.ui.test.longClick
|
||||
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.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.extensions.clickWithAssertion
|
||||
import com.tangem.common.utils.assertClipboardTextEquals
|
||||
import com.tangem.common.utils.clearClipboard
|
||||
import com.tangem.scenarios.openMainScreen
|
||||
import com.tangem.scenarios.synchronizeAddresses
|
||||
import com.tangem.common.extensions.*
|
||||
import com.tangem.common.utils.*
|
||||
import com.tangem.datasource.api.common.config.ApiConfig
|
||||
import com.tangem.datasource.api.common.config.ApiEnvironment
|
||||
import com.tangem.scenarios.*
|
||||
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 io.qameta.allure.kotlin.AllureId
|
||||
import io.qameta.allure.kotlin.junit4.DisplayName
|
||||
|
|
@ -18,6 +23,7 @@ import org.junit.Test
|
|||
@HiltAndroidTest
|
||||
class MainScreenActionButtonsTest : BaseTestCase() {
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.MoonPay, ApiEnvironment.PROD))
|
||||
@AllureId("79")
|
||||
@DisplayName("Action buttons (long tap): validate UI")
|
||||
@Test
|
||||
|
|
@ -121,7 +127,6 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
|||
@Test
|
||||
fun clickOnBuyButtonTest() {
|
||||
val tokenTitle = "Bitcoin"
|
||||
val tokenSymbol = "BTC"
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
|
|
@ -160,12 +165,6 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
|||
step("Assert fiat currency icon is displayed") {
|
||||
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 +280,18 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
|||
step("Click on 'Receive' button") {
|
||||
onTokenActionsBottomSheet { receiveButton.performClick() }
|
||||
}
|
||||
step("Assert 'Token receive warning' bottom sheet is displayed") {
|
||||
waitForIdle()
|
||||
step("Go to QR code bottom sheet") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
onTokenReceiveWarningBottomSheet {
|
||||
bottomSheet.assertIsDisplayed()
|
||||
}
|
||||
goToQrCodeBottomSheet()
|
||||
}
|
||||
}
|
||||
step("Click on 'Got it' button") {
|
||||
onTokenReceiveWarningBottomSheet { gotItButton.performClick() }
|
||||
}
|
||||
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() }
|
||||
step("Check QR code bottom sheet") {
|
||||
checkQrCodeBottomSheetScenario()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ApiEnv(ApiEnvConfig(ApiConfig.ID.MoonPay, ApiEnvironment.PROD))
|
||||
@AllureId("85")
|
||||
@DisplayName("Action buttons (long tap): check 'Sell' button")
|
||||
@Test
|
||||
|
|
@ -342,10 +318,10 @@ class MainScreenActionButtonsTest : BaseTestCase() {
|
|||
}
|
||||
}
|
||||
}
|
||||
step("Assert 'Receive' button is displayed") {
|
||||
step("Assert 'Sell' button is displayed") {
|
||||
onTokenActionsBottomSheet { sellButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Click on 'Receive' button") {
|
||||
step("Click on 'Sell' button") {
|
||||
onTokenActionsBottomSheet { sellButton.performClick() }
|
||||
}
|
||||
step("Assert Chrome Browser is opened") {
|
||||
|
|
@ -391,4 +367,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,142 @@
|
|||
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 'My wallets' item is not displayed") {
|
||||
onSendAddressScreen {
|
||||
recentAddressItem(
|
||||
recipientAddress = ETHEREUM_ADDRESS,
|
||||
isMyWallet = true
|
||||
).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,355 @@
|
|||
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 recentTransactionAmount1 = "DOGE 0.0192"
|
||||
private val recentTransactionAmount2 = "DOGE 0.10"
|
||||
private val recentTransactionAmount3 = "DOGE 0.005"
|
||||
|
||||
@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("Click on top bar 'Close' button") {
|
||||
waitForIdle()
|
||||
onSendConfirmScreen { closeButton.clickWithAssertion() }
|
||||
}
|
||||
step("Assert 'Send Address' screen is displayed") {
|
||||
waitForIdle()
|
||||
onSendAddressScreen { container.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert address text field is displayed") {
|
||||
onSendAddressScreen { addressTextField.assertIsDisplayed() }
|
||||
}
|
||||
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 = "Dogecoin"
|
||||
val sendAmount = "1"
|
||||
val txHistoryScenarioState = "Empty"
|
||||
|
||||
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 not displayed") {
|
||||
onSendAddressScreen { recentAddressesTitle.assertIsNotDisplayed() }
|
||||
}
|
||||
step("Assert recent address item is not displayed") {
|
||||
onSendAddressScreen { recentAddressItem(DOGECOIN_ADDRESS).assertIsNotDisplayed() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4598")
|
||||
@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"
|
||||
|
||||
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 = recentTransactionAmount1)
|
||||
}
|
||||
step("Check recent address item №2") {
|
||||
checkRecentAddressItem(address = shortenedRecipientAddress, description = recentTransactionAmount2)
|
||||
}
|
||||
step("Check recent address item №3") {
|
||||
checkRecentAddressItem(address = recipientAddressBase + "k", description = recentTransactionAmount2)
|
||||
}
|
||||
step("Check recent address item №4") {
|
||||
checkRecentAddressItem(address = recipientAddressBase + "c", description = recentTransactionAmount2)
|
||||
}
|
||||
step("Check recent address item №5") {
|
||||
checkRecentAddressItem(address = recipientAddressBase + "d", description = recentTransactionAmount2)
|
||||
}
|
||||
step("Check recent address item №6") {
|
||||
checkRecentAddressItem(address = recipientAddressBase + "e", description = recentTransactionAmount2)
|
||||
}
|
||||
step("Swipe up") {
|
||||
waitForIdle()
|
||||
swipeVertical(SwipeDirection.UP, startHeightRatio = 0.6f)
|
||||
}
|
||||
step("Check recent address item №7") {
|
||||
checkRecentAddressItem(address = recipientAddressBase + "f", description = recentTransactionAmount2)
|
||||
}
|
||||
step("Check recent address item №8") {
|
||||
checkRecentAddressItem(address = recipientAddressBase + "g", description = recentTransactionAmount2)
|
||||
}
|
||||
step("Check recent address item №9") {
|
||||
checkRecentAddressItem(address = recipientAddressBase + "o", description = recentTransactionAmount2)
|
||||
}
|
||||
step("Check recent address item №10") {
|
||||
checkRecentAddressItem(address = recipientAddressBase + "p", description = recentTransactionAmount2)
|
||||
}
|
||||
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"
|
||||
|
||||
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 = recentTransactionAmount1)
|
||||
}
|
||||
step("Check recent address item №2") {
|
||||
checkRecentAddressItem(address = DOGECOIN_ADDRESS, description = recentTransactionAmount3)
|
||||
}
|
||||
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,629 @@
|
|||
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("4009")
|
||||
@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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@AllureId("4567")
|
||||
@DisplayName("Send (address screen): check address field")
|
||||
@Test
|
||||
fun checkSendAddressScreenTest() {
|
||||
val tokenName = "Ethereum"
|
||||
val sendAmount = "1"
|
||||
val hint = getResourceString(R.string.send_enter_address_field_ens)
|
||||
val recipient = getResourceString(R.string.send_recipient)
|
||||
|
||||
setupHooks().run {
|
||||
step("Open 'Main Screen'") {
|
||||
openMainScreen()
|
||||
}
|
||||
step("Synchronize addresses") {
|
||||
synchronizeAddresses()
|
||||
}
|
||||
step("Open 'Send Address' screen") {
|
||||
openSendAddressScreen(tokenName, sendAmount)
|
||||
}
|
||||
step("Assert 'Address' screen title is displayed") {
|
||||
onSendAddressScreen { topAppBarTitle.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert address text field title is displayed") {
|
||||
onSendAddressScreen { addressTextFieldTitle.assertTextContains(recipient) }
|
||||
}
|
||||
step("Assert address text field is displayed") {
|
||||
onSendAddressScreen { addressTextField.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert address text field hint contains text '$hint'") {
|
||||
onSendAddressScreen { addressTextFieldHint.assertTextContains(hint) }
|
||||
}
|
||||
step("Assert 'QR' button is displayed") {
|
||||
onSendAddressScreen { qrButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Paste' button is displayed") {
|
||||
onSendAddressScreen { addressPasteButton.assertIsDisplayed() }
|
||||
}
|
||||
step("Assert recipient network caution is displayed for network '$tokenName'") {
|
||||
onSendAddressScreen { recipientNetworkCaution(tokenName).assertIsDisplayed() }
|
||||
}
|
||||
step("Assert 'Next' button is disabled") {
|
||||
onSendAddressScreen { nextButton.assertIsNotEnabled() }
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,309 @@
|
|||
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.constants.TestConstants.WAIT_UNTIL_TIMEOUT
|
||||
import com.tangem.common.constants.TestConstants.WAIT_UNTIL_TIMEOUT_LONG
|
||||
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 {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT) {
|
||||
refreshButton.clickWithAssertion()
|
||||
refreshButton.assertDoesNotExist()
|
||||
}
|
||||
}
|
||||
}
|
||||
step("Check network fee block") {
|
||||
flakySafely(WAIT_UNTIL_TIMEOUT_LONG) {
|
||||
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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,6 +11,9 @@
|
|||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-permission android:name="android.permission.WAKE_LOCK" />
|
||||
<uses-permission android:name="com.google.android.gms.permission.AD_ID" />
|
||||
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" tools:node="remove" />
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" tools:node="remove" />
|
||||
|
||||
<uses-feature android:name="android.hardware.camera" />
|
||||
<uses-feature android:name="android.hardware.camera.autofocus" />
|
||||
|
|
@ -276,6 +279,17 @@
|
|||
android:host="onboard-visa"
|
||||
android:scheme="tangem" />
|
||||
</intent-filter>
|
||||
<intent-filter android:autoVerify="true">
|
||||
<action android:name="android.intent.action.VIEW" />
|
||||
|
||||
<category android:name="android.intent.category.DEFAULT" />
|
||||
<category android:name="android.intent.category.BROWSABLE" />
|
||||
|
||||
<data
|
||||
android:scheme="https"
|
||||
android:host="tangem.com"
|
||||
android:path="/pay-app" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
|
||||
<!-- Disable android.startup completely. Used for Worker according doc -->
|
||||
|
|
@ -310,4 +324,8 @@
|
|||
</intent-filter>
|
||||
</service>
|
||||
</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>
|
||||
|
|
|
|||
|
|
@ -1 +1 @@
|
|||
Subproject commit 76978242504ab90803b99e2b0575f7ebe8e69335
|
||||
Subproject commit 5fc86d29bc2c0dc7c057ab0242b2cfa3e9f48daf
|
||||
|
|
@ -5,7 +5,9 @@ import com.tangem.TangemSdkLogger
|
|||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
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.utils.TrackingContextProxy
|
||||
import com.tangem.core.configtoggle.blockchain.ExcludedBlockchainsManager
|
||||
import com.tangem.core.configtoggle.feature.FeatureTogglesManager
|
||||
import com.tangem.core.decompose.di.GlobalUiMessageSender
|
||||
|
|
@ -151,4 +153,8 @@ interface ApplicationEntryPoint {
|
|||
fun getHotWalletFeatureToggles(): HotWalletFeatureToggles
|
||||
|
||||
fun getWcInitializeUseCase(): WcInitializeUseCase
|
||||
|
||||
fun getTrackingContextProxy(): TrackingContextProxy
|
||||
|
||||
fun getABTestsManager(): ABTestsManager
|
||||
}
|
||||
|
|
@ -302,7 +302,9 @@ class MainActivity : AppCompatActivity(), ActivityResultCallbackHolder {
|
|||
}
|
||||
|
||||
private fun createAppThemeModeFlow(): SharedFlow<AppThemeMode> {
|
||||
val tangemApplication = application as TangemApplication
|
||||
val tangemApplication = requireNotNull(application as? TangemApplication) {
|
||||
"Application is null"
|
||||
}
|
||||
|
||||
return tangemApplication.getAppThemeModeUseCase()
|
||||
.filterNotNull()
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@ import com.tangem.blockchain.network.BlockchainSdkRetrofitBuilder
|
|||
import com.tangem.blockchainsdk.BlockchainSDKFactory
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.common.routing.AppRouter
|
||||
import com.tangem.core.abtests.manager.ABTestsManager
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.core.analytics.api.ParamsInterceptor
|
||||
import com.tangem.core.analytics.filter.OneTimeEventFilter
|
||||
|
|
@ -240,6 +241,12 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
private val wcInitializeUseCase
|
||||
get() = entryPoint.getWcInitializeUseCase()
|
||||
|
||||
private val trackingContextProxy
|
||||
get() = entryPoint.getTrackingContextProxy()
|
||||
|
||||
private val abTestsManager: ABTestsManager
|
||||
get() = entryPoint.getABTestsManager()
|
||||
|
||||
// endregion
|
||||
|
||||
private val appScope = MainScope()
|
||||
|
|
@ -310,6 +317,8 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
initWithConfigDependency(environmentConfig = environmentConfigStorage.initialize())
|
||||
}
|
||||
|
||||
abTestsManager.init()
|
||||
|
||||
appScope.launch {
|
||||
launch(Dispatchers.IO) {
|
||||
loadNativeLibraries()
|
||||
|
|
@ -385,6 +394,7 @@ open class TangemApplication : Application(), ImageLoaderFactory, Configuration.
|
|||
userWalletsListRepository = userWalletsListRepository,
|
||||
tangemHotSdk = tangemHotSdk,
|
||||
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,99 @@
|
|||
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.core.analytics.models.AnalyticsParam
|
||||
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 = AnalyticsParam.ProductType.MobileWallet.value,
|
||||
firmware = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ class BlockchainApiExceptionEvent(
|
|||
selectedHost: String,
|
||||
exceptionHost: String,
|
||||
error: String,
|
||||
blockchain: String,
|
||||
) : AnalyticsEvent(
|
||||
category = "BlockchainSdk",
|
||||
event = "Exception",
|
||||
|
|
@ -13,5 +14,6 @@ class BlockchainApiExceptionEvent(
|
|||
AnalyticsParam.BLOCKCHAIN_SELECTED_HOST to selectedHost,
|
||||
AnalyticsParam.BLOCKCHAIN_EXCEPTION_HOST to exceptionHost,
|
||||
AnalyticsParam.ERROR_DESCRIPTION to error,
|
||||
AnalyticsParam.BLOCKCHAIN to blockchain,
|
||||
),
|
||||
)
|
||||
|
|
@ -1,6 +1,8 @@
|
|||
package com.tangem.tap.common.analytics.handlers
|
||||
|
||||
import com.tangem.blockchain.common.Blockchain
|
||||
import com.tangem.blockchain.common.ExceptionHandlerOutput
|
||||
import com.tangem.blockchainsdk.utils.toNetworkId
|
||||
import com.tangem.core.analytics.api.AnalyticsErrorHandler
|
||||
import com.tangem.tap.common.analytics.events.BlockchainApiExceptionEvent
|
||||
import javax.inject.Inject
|
||||
|
|
@ -8,12 +10,13 @@ import javax.inject.Inject
|
|||
class BlockchainExceptionHandler @Inject constructor(
|
||||
private val analyticsErrorHandler: AnalyticsErrorHandler,
|
||||
) : ExceptionHandlerOutput {
|
||||
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String) {
|
||||
override fun handleApiSwitch(currentHost: String, nextHost: String, message: String, blockchain: Blockchain) {
|
||||
analyticsErrorHandler.sendErrorEvent(
|
||||
BlockchainApiExceptionEvent(
|
||||
selectedHost = nextHost,
|
||||
exceptionHost = currentHost,
|
||||
error = message,
|
||||
blockchain = blockchain.toNetworkId(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -2,13 +2,13 @@ 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
|
||||
import com.tangem.domain.card.analytics.IntroductionProcess
|
||||
import com.tangem.domain.card.analytics.ParamCardCurrencyConverter
|
||||
import com.tangem.domain.card.common.util.cardTypesResolver
|
||||
import com.tangem.domain.models.scan.ProductType
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.analytics.events.AnalyticsParam
|
||||
import com.tangem.tap.common.extensions.inject
|
||||
import com.tangem.tap.features.demo.DemoHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -50,17 +50,17 @@ class CardContextInterceptor(
|
|||
if (scanResponse.productType != ProductType.Ring && userWalletId != null) {
|
||||
val isWalletWithRing = runBlocking { walletsRepository.isWalletWithRing(userWalletId) }
|
||||
|
||||
if (isWalletWithRing) return "Ring"
|
||||
if (isWalletWithRing) return AnalyticsParam.ProductType.Ring.value
|
||||
}
|
||||
|
||||
return when (scanResponse.productType) {
|
||||
ProductType.Note -> "Note"
|
||||
ProductType.Twins -> "Twin"
|
||||
ProductType.Wallet -> "Wallet"
|
||||
ProductType.Wallet2 -> "Wallet 2.0"
|
||||
ProductType.Ring -> "Ring"
|
||||
ProductType.Start2Coin -> "Start2Coin"
|
||||
ProductType.Visa -> "VISA"
|
||||
ProductType.Note -> AnalyticsParam.ProductType.Note.value
|
||||
ProductType.Twins -> AnalyticsParam.ProductType.Twins.value
|
||||
ProductType.Wallet -> AnalyticsParam.ProductType.Wallet.value
|
||||
ProductType.Wallet2 -> AnalyticsParam.ProductType.Wallet2.value
|
||||
ProductType.Ring -> AnalyticsParam.ProductType.Ring.value
|
||||
ProductType.Start2Coin -> AnalyticsParam.ProductType.Start2Coin.value
|
||||
ProductType.Visa -> AnalyticsParam.ProductType.Visa.value
|
||||
else -> if (DemoHelper.isDemoCard(scanResponse)) getDemoCardProductType() else "Other"
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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] = AnalyticsParam.ProductType.MobileWallet.value
|
||||
}
|
||||
|
||||
companion object {
|
||||
fun id(): String = HotWalletContextInterceptor::class.java.simpleName
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,7 @@ import com.tangem.domain.models.scan.ScanResponse
|
|||
*/
|
||||
class LinkedCardContextInterceptor(
|
||||
scanResponse: ScanResponse,
|
||||
val parent: LinkedCardContextInterceptor? = null,
|
||||
val parent: ParamsInterceptor? = null,
|
||||
) : ParamsInterceptor {
|
||||
|
||||
private val contextInterceptor = CardContextInterceptor(scanResponse)
|
||||
|
|
|
|||
|
|
@ -3,7 +3,9 @@ package com.tangem.tap.common.extensions
|
|||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWallet
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.HotWalletContextInterceptor
|
||||
import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterceptor
|
||||
|
||||
/**
|
||||
|
|
@ -13,8 +15,7 @@ import com.tangem.tap.common.analytics.paramsInterceptor.LinkedCardContextInterc
|
|||
/**
|
||||
* Sets the new context
|
||||
*/
|
||||
fun Analytics.setContext(scanResponse: ScanResponse) {
|
||||
val userWalletId = UserWalletIdBuilder.scanResponse(scanResponse).build()
|
||||
fun Analytics.setContext(userWalletId: UserWalletId?, scanResponse: ScanResponse) {
|
||||
if (userWalletId != null) {
|
||||
setUserId(userWalletId.stringValue)
|
||||
}
|
||||
|
|
@ -24,19 +25,46 @@ fun Analytics.setContext(scanResponse: ScanResponse) {
|
|||
|
||||
fun Analytics.setContext(userWallet: UserWallet) {
|
||||
setUserId(userWallet.walletId.stringValue)
|
||||
// TODO add product type for hot ([REDACTED_TASK_KEY] [Hot Wallet] Analytics)
|
||||
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
addParamsInterceptor(LinkedCardContextInterceptor(userWallet.scanResponse))
|
||||
when (userWallet) {
|
||||
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
|
||||
*/
|
||||
fun Analytics.eraseContext() {
|
||||
clearUserId()
|
||||
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)
|
||||
}
|
||||
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||
val newContext = LinkedCardContextInterceptor(scanResponse, parent = currentContext)
|
||||
|
||||
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.
|
||||
*/
|
||||
fun Analytics.removeContext() {
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id()) as? LinkedCardContextInterceptor
|
||||
val previousContext = currentContext?.parent ?: return
|
||||
val currentContext = removeParamsInterceptor(LinkedCardContextInterceptor.id())
|
||||
?: removeParamsInterceptor(HotWalletContextInterceptor.id())
|
||||
|
||||
val previousContext = when (currentContext) {
|
||||
is LinkedCardContextInterceptor -> currentContext.parent
|
||||
is HotWalletContextInterceptor -> currentContext.parent
|
||||
else -> null
|
||||
} ?: return
|
||||
|
||||
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)
|
||||
}
|
||||
|
|
@ -1,6 +0,0 @@
|
|||
package com.tangem.tap.common.extensions
|
||||
|
||||
fun String.removePrefixOrNull(prefix: String): String? = when {
|
||||
startsWith(prefix) -> substring(prefix.length)
|
||||
else -> null
|
||||
}
|
||||
|
|
@ -54,8 +54,8 @@ private class CoilTimberLogger : Logger {
|
|||
|
||||
override fun log(tag: String, priority: Int, message: String?, throwable: Throwable?) {
|
||||
with(Timber.tag(COIL_LOG_TAG)) {
|
||||
throwable?.let { e -> e(e, message) }
|
||||
message?.let { msg -> d(msg) }
|
||||
if (throwable != null) e(throwable, message)
|
||||
if (message != null) d(message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -54,8 +54,8 @@ class PushNotificationDelegate(private val context: Context) {
|
|||
.setContentIntent(pendingIntent)
|
||||
.setVibrate(vibratePattern)
|
||||
.apply {
|
||||
imageUrl?.let { uri ->
|
||||
val bitmap = getBitmapImageFromUrl(uri)
|
||||
if (imageUrl != null) {
|
||||
val bitmap = getBitmapImageFromUrl(imageUrl)
|
||||
setStyle(
|
||||
NotificationCompat
|
||||
.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) {
|
||||
val notificationChannel = NotificationChannel(
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@ import com.tangem.tap.features.welcome.redux.WelcomeReducer
|
|||
import com.tangem.tap.proxy.redux.DaggerGraphReducer
|
||||
import org.rekotlin.Action
|
||||
|
||||
@Suppress("CanBeNonNullable")
|
||||
fun appReducer(action: Action, state: AppState?): AppState {
|
||||
requireNotNull(state)
|
||||
if (action is AppAction.RestoreState) return action.state
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import kotlinx.coroutines.Dispatchers
|
|||
import kotlinx.coroutines.flow.*
|
||||
import org.rekotlin.Middleware
|
||||
|
||||
@Suppress("MemberNameEqualsClassName")
|
||||
internal object LegacyMiddleware {
|
||||
private val prepareDetailsScreenJobHolder = JobHolder()
|
||||
|
||||
|
|
@ -29,7 +30,14 @@ internal object LegacyMiddleware {
|
|||
val walletsRepository = store.inject(DaggerGraphState::walletsRepository)
|
||||
|
||||
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 ->
|
||||
val initializedAppSettingsStateContent = initializeAppSettingsState(
|
||||
shouldSaveUserWallets = walletsRepository.shouldSaveUserWalletsSync(),
|
||||
|
|
@ -78,6 +86,7 @@ internal object LegacyMiddleware {
|
|||
isHidingEnabled = store.inject(DaggerGraphState::balanceHidingRepository)
|
||||
.getBalanceHidingSettings().isHidingEnabledInSettings,
|
||||
needEnrollBiometrics = runCatching(tangemSdkManager::needEnrollBiometrics).getOrNull() == true,
|
||||
hasSecuredWallets = store.inject(DaggerGraphState::userWalletsListRepository).hasSecuredWallets(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -41,8 +41,8 @@ object SimpleCancelableAlertDialog {
|
|||
context: Context,
|
||||
): AlertDialog {
|
||||
return MaterialAlertDialogBuilder(context, R.style.CustomMaterialDialog).apply {
|
||||
setTitle(titleRes?.let { context.getString(it) } ?: title)
|
||||
setMessage(messageRes?.let { context.getString(it) } ?: message)
|
||||
setTitle(if (titleRes != null) context.getString(titleRes) else title)
|
||||
setMessage(if (messageRes != null) context.getString(messageRes) else message)
|
||||
setPositiveButton(context.getText(primaryButtonRes)) { _, _ -> primaryButtonAction() }
|
||||
if (secondaryButtonRes != null) {
|
||||
setNegativeButton(context.getText(secondaryButtonRes)) { _, _ -> secondaryButtonAction() }
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ internal class CustomTabsUrlOpener : UrlOpener {
|
|||
|
||||
private fun openUrl(url: String, context: Context) {
|
||||
if (url.isEmpty()) return
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, url.toUri())
|
||||
val browserIntent = Intent(Intent.ACTION_VIEW, url.trim().toUri())
|
||||
runCatching {
|
||||
if (checkCustomTabsAvailability(context, browserIntent)) {
|
||||
context.startActivity(browserIntent)
|
||||
|
|
|
|||
|
|
@ -2,24 +2,31 @@ package com.tangem.tap.data
|
|||
|
||||
import android.content.Context
|
||||
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.getObjectMapSync
|
||||
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.domain.visa.model.VisaAuthTokens
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.TangemPayAuthTokens
|
||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.hilt.android.qualifiers.ApplicationContext
|
||||
import kotlinx.coroutines.withContext
|
||||
import javax.inject.Inject
|
||||
import javax.inject.Singleton
|
||||
import kotlin.text.encodeToByteArray
|
||||
|
||||
private const val DEFAULT_KEY = "tangem_pay_default_key"
|
||||
private const val ORDER_ID_KEY = "tangem_pay_order_id_key"
|
||||
private const val AUTH_TOKENS_DEFAULT_KEY = "tangem_pay_default_key"
|
||||
private const val WITHDRAW_ORDER_ID_KEY = "tangem_pay_withdraw_order_id_key"
|
||||
|
||||
@Singleton
|
||||
internal class DefaultTangemPayStorage @Inject constructor(
|
||||
@ApplicationContext applicationContext: Context,
|
||||
@NetworkMoshi moshi: Moshi,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
) : TangemPayStorage {
|
||||
|
||||
private val secureStorage by lazy {
|
||||
|
|
@ -29,54 +36,115 @@ internal class DefaultTangemPayStorage @Inject constructor(
|
|||
name = "tangem_pay_storage",
|
||||
)
|
||||
}
|
||||
private val moshi by lazy {
|
||||
Moshi.Builder()
|
||||
.add(KotlinJsonAdapterFactory())
|
||||
.build()
|
||||
|
||||
private val tokensAdapter by lazy { moshi.adapter(TangemPayAuthTokens::class.java) }
|
||||
|
||||
override suspend fun storeCustomerWalletAddress(userWalletId: UserWalletId, customerWalletAddress: String) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore
|
||||
.store(PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId), customerWalletAddress)
|
||||
}
|
||||
}
|
||||
|
||||
private val tokensAdapter by lazy { moshi.adapter(VisaAuthTokens::class.java) }
|
||||
override suspend fun getCustomerWalletAddress(userWalletId: UserWalletId): String? {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.getSyncOrNull(key = PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId))
|
||||
.takeIf { !it.isNullOrEmpty() }
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: VisaAuthTokens) =
|
||||
override suspend fun storeAuthTokens(customerWalletAddress: String, tokens: TangemPayAuthTokens) =
|
||||
withContext(dispatcherProvider.io) {
|
||||
val json = tokensAdapter.toJson(tokens)
|
||||
|
||||
secureStorage.store(
|
||||
json.encodeToByteArray(throwOnInvalidSequence = true),
|
||||
createKey(customerWalletAddress),
|
||||
createAuthTokensKey(customerWalletAddress),
|
||||
)
|
||||
}
|
||||
|
||||
override suspend fun getAuthTokens(customerWalletAddress: String): VisaAuthTokens? =
|
||||
override suspend fun getAuthTokens(customerWalletAddress: String): TangemPayAuthTokens? =
|
||||
withContext(dispatcherProvider.io) {
|
||||
secureStorage.get(createKey(customerWalletAddress))
|
||||
secureStorage.get(createAuthTokensKey(customerWalletAddress))
|
||||
?.decodeToString(throwOnInvalidSequence = true)
|
||||
?.let(tokensAdapter::fromJson)
|
||||
}
|
||||
|
||||
override suspend fun storeOrderId(customerWalletAddress: String, orderId: String) {
|
||||
withContext(dispatcherProvider.io) {
|
||||
secureStorage.store(
|
||||
orderId.encodeToByteArray(throwOnInvalidSequence = true),
|
||||
createOrderIdKey(customerWalletAddress),
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), orderId)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getOrderId(customerWalletAddress: String): String? {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
appPreferencesStore.getSyncOrNull(key = PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress))
|
||||
.takeIf { !it.isNullOrEmpty() }
|
||||
}
|
||||
}
|
||||
|
||||
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) {
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), "")
|
||||
}
|
||||
|
||||
override suspend fun storeCheckCustomerWalletResult(userWalletId: UserWalletId, isPaeraCustomer: Boolean) {
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayCheckCustomerByWalletId(userWalletId), isPaeraCustomer)
|
||||
}
|
||||
|
||||
override suspend fun checkCustomerWalletResult(userWalletId: UserWalletId): Boolean? {
|
||||
return appPreferencesStore.getSyncOrNull(PreferencesKeys.getTangemPayCheckCustomerByWalletId(userWalletId))
|
||||
}
|
||||
|
||||
override suspend fun clearAll(userWalletId: UserWalletId, customerWalletAddress: String) =
|
||||
withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(createAuthTokensKey(customerWalletAddress))
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayCustomerWalletAddressKey(userWalletId), "")
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayOrderIdKey(customerWalletAddress), "")
|
||||
appPreferencesStore.store(PreferencesKeys.getTangemPayAddToWalletKey(customerWalletAddress), false)
|
||||
}
|
||||
|
||||
override suspend fun storeWithdrawOrder(userWalletId: UserWalletId, orderId: String) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val orders = mutablePreferences.getObjectMap<String>(PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY)
|
||||
.plus(createWithdrawOrderIdKey(userWalletId) to orderId)
|
||||
mutablePreferences.setObjectMap(
|
||||
key = PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY,
|
||||
value = orders,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getOrderId(customerWalletAddress: String): String? = withContext(dispatcherProvider.io) {
|
||||
secureStorage.get(createOrderIdKey(customerWalletAddress))?.decodeToString(throwOnInvalidSequence = true)
|
||||
override suspend fun getWithdrawOrderId(userWalletId: UserWalletId): String? {
|
||||
val orders = appPreferencesStore.getObjectMapSync<String>(PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY)
|
||||
return orders[createWithdrawOrderIdKey(userWalletId)]
|
||||
}
|
||||
|
||||
override suspend fun clearOrderId(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||
override suspend fun deleteWithdrawOrder(userWalletId: UserWalletId) {
|
||||
appPreferencesStore.editData { mutablePreferences ->
|
||||
val orders = mutablePreferences.getObjectMap<String>(PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY)
|
||||
.minus(createWithdrawOrderIdKey(userWalletId))
|
||||
mutablePreferences.setObjectMap(
|
||||
key = PreferencesKeys.TANGEM_PAY_WITHDRAW_ORDERS_KEY,
|
||||
value = orders,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clearAll(customerWalletAddress: String) = withContext(dispatcherProvider.io) {
|
||||
secureStorage.delete(createKey(customerWalletAddress))
|
||||
secureStorage.delete(createOrderIdKey(customerWalletAddress))
|
||||
}
|
||||
private fun createAuthTokensKey(address: String): String = "${AUTH_TOKENS_DEFAULT_KEY}_$address"
|
||||
|
||||
private fun createKey(address: String): String = "${DEFAULT_KEY}_$address"
|
||||
|
||||
private fun createOrderIdKey(address: String): String = "${ORDER_ID_KEY}_$address"
|
||||
private fun createWithdrawOrderIdKey(userWalletId: UserWalletId): String = "${WITHDRAW_ORDER_ID_KEY}_$userWalletId"
|
||||
}
|
||||
|
|
@ -1,14 +1,18 @@
|
|||
package com.tangem.tap.di
|
||||
|
||||
import com.tangem.blockchainsdk.utils.ExcludedBlockchains
|
||||
import com.tangem.datasource.exchangeservice.swap.ExpressServiceLoader
|
||||
import com.tangem.datasource.api.moonpay.MoonPayApi
|
||||
import com.tangem.datasource.local.config.environment.EnvironmentConfigStorage
|
||||
import com.tangem.domain.card.ScanCardUseCase
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.exchange.RampStateManager
|
||||
import com.tangem.domain.express.ExpressServiceFetcher
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
import com.tangem.domain.wallets.usecase.GetSelectedWalletUseCase
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.scanCard.repository.DefaultScanCardRepository
|
||||
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.utils.Provider
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
|
|
@ -43,17 +47,15 @@ internal object ActivityModule {
|
|||
@Singleton
|
||||
fun provideDefaultRampManager(
|
||||
appStateHolder: AppStateHolder,
|
||||
expressServiceLoader: ExpressServiceLoader,
|
||||
expressServiceFetcher: ExpressServiceFetcher,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
excludedBlockchains: ExcludedBlockchains,
|
||||
dispatchers: CoroutineDispatcherProvider,
|
||||
): RampStateManager {
|
||||
return DefaultRampManager(
|
||||
sellService = Provider { requireNotNull(appStateHolder.sellService) },
|
||||
expressServiceLoader = expressServiceLoader,
|
||||
expressServiceFetcher = expressServiceFetcher,
|
||||
currenciesRepository = currenciesRepository,
|
||||
dispatchers = dispatchers,
|
||||
excludedBlockchains = excludedBlockchains,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -63,4 +65,19 @@ internal object ActivityModule {
|
|||
fun provideActivityDelayedWorkCoroutineScope(): CoroutineScope {
|
||||
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.Vibrator
|
||||
import android.os.VibratorManager
|
||||
import com.tangem.tap.common.haptic.DefaultVibratorHapticManager
|
||||
import com.tangem.core.ui.haptic.TangemHapticEffect
|
||||
import com.tangem.core.ui.haptic.VibratorHapticManager
|
||||
import com.tangem.tap.common.haptic.DefaultVibratorHapticManager
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -22,10 +22,14 @@ class HapticModule {
|
|||
@Singleton
|
||||
fun provideHapticManager(@ApplicationContext context: Context): VibratorHapticManager {
|
||||
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
|
||||
} 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()) {
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
|||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.sdk.impl.DefaultTangemSdkManager
|
||||
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.visa.VisaCardScanHandler
|
||||
import dagger.Module
|
||||
|
|
@ -27,6 +28,7 @@ internal class TangemSdkManagerModule {
|
|||
cardSdkConfigRepository: CardSdkConfigRepository,
|
||||
visaCardScanHandler: VisaCardScanHandler,
|
||||
visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||
tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||
onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
): TangemSdkManager {
|
||||
return if (BuildConfig.MOCK_DATA_SOURCE) {
|
||||
|
|
@ -37,6 +39,7 @@ internal class TangemSdkManagerModule {
|
|||
resources = context.resources,
|
||||
visaCardScanHandler = visaCardScanHandler,
|
||||
visaCardActivationTaskFactory = visaCardActivationTaskFactory,
|
||||
tangemPayChallengeTaskFactory = tangemPayChallengeTaskFactory,
|
||||
onboardingV2FeatureToggles = onboardingV2FeatureToggles,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
package com.tangem.tap.di.analytics
|
||||
|
||||
import com.tangem.core.abtests.manager.ABTestsManager
|
||||
import com.tangem.core.analytics.AppInstanceIdProvider
|
||||
import com.tangem.core.analytics.utils.AnalyticsContextProxy
|
||||
import com.tangem.domain.analytics.ChangeCardAnalyticsContextUseCase
|
||||
import com.tangem.tap.common.analytics.DefaultAnalyticsContextProxy
|
||||
import com.tangem.tap.common.analytics.DefaultChangeCardAnalyticsContextUseCase
|
||||
import com.tangem.core.analytics.utils.TrackingContextProxy
|
||||
import com.tangem.tap.common.analytics.DefaultTrackingContextProxy
|
||||
import com.tangem.tap.common.analytics.handlers.firebase.FirebaseAppInstanceIdProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
|
|
@ -18,13 +17,8 @@ internal object AnalyticsModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideChangeCardAnalyticsContextUseCase(): ChangeCardAnalyticsContextUseCase {
|
||||
return DefaultChangeCardAnalyticsContextUseCase()
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideAnalyticsContextProxy(): AnalyticsContextProxy = DefaultAnalyticsContextProxy()
|
||||
fun provideAnalyticsContextProxy(abtestsManager: ABTestsManager): TrackingContextProxy =
|
||||
DefaultTrackingContextProxy(abtestsManager)
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
|
|
|
|||
|
|
@ -3,8 +3,14 @@ package com.tangem.tap.di.domain
|
|||
import com.tangem.domain.account.featuretoggle.AccountsFeatureToggles
|
||||
import com.tangem.domain.account.fetcher.SingleAccountListFetcher
|
||||
import com.tangem.domain.account.repository.AccountsCRUDRepository
|
||||
import com.tangem.domain.account.status.supplier.SingleAccountStatusListSupplier
|
||||
import com.tangem.domain.account.status.usecase.ArchiveCryptoPortfolioUseCase
|
||||
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.usecase.*
|
||||
import com.tangem.feature.referral.data.ExternalReferralRepository
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import dagger.Module
|
||||
import dagger.Provides
|
||||
import dagger.hilt.InstallIn
|
||||
|
|
@ -32,17 +38,27 @@ internal object AccountDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideUpdateCryptoPortfolioUseCase(
|
||||
singleAccountListFetcher: SingleAccountListFetcher,
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
): UpdateCryptoPortfolioUseCase {
|
||||
return UpdateCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
return UpdateCryptoPortfolioUseCase(
|
||||
singleAccountListFetcher = singleAccountListFetcher,
|
||||
crudRepository = accountsCRUDRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideArchiveCryptoPortfolioUseCase(
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
referralRepository: ExternalReferralRepository,
|
||||
): ArchiveCryptoPortfolioUseCase {
|
||||
return ArchiveCryptoPortfolioUseCase(crudRepository = accountsCRUDRepository)
|
||||
return ArchiveCryptoPortfolioUseCase(
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
crudRepository = accountsCRUDRepository,
|
||||
referralRepository = referralRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
@ -50,10 +66,12 @@ internal object AccountDomainModule {
|
|||
fun provideRecoverCryptoPortfolioUseCase(
|
||||
accountsCRUDRepository: AccountsCRUDRepository,
|
||||
mainAccountTokensMigration: MainAccountTokensMigration,
|
||||
cryptoCurrencyBalanceFetcher: CryptoCurrencyBalanceFetcher,
|
||||
): RecoverCryptoPortfolioUseCase {
|
||||
return RecoverCryptoPortfolioUseCase(
|
||||
crudRepository = accountsCRUDRepository,
|
||||
mainAccountTokensMigration = mainAccountTokensMigration,
|
||||
cryptoCurrencyBalanceFetcher = cryptoCurrencyBalanceFetcher,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -82,4 +100,16 @@ internal object AccountDomainModule {
|
|||
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
|
||||
@Singleton
|
||||
fun provideIsDemoCardUseCase(): IsDemoCardUseCase = IsDemoCardUseCase(config = DemoConfig())
|
||||
fun provideIsDemoCardUseCase(): IsDemoCardUseCase = IsDemoCardUseCase(config = DemoConfig)
|
||||
|
||||
@Provides
|
||||
@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,8 @@
|
|||
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.account.supplier.SingleAccountListSupplier
|
||||
import com.tangem.domain.networks.single.SingleNetworkStatusSupplier
|
||||
import com.tangem.domain.nft.*
|
||||
import com.tangem.domain.nft.repository.NFTRepository
|
||||
|
|
@ -24,9 +27,13 @@ internal object NFTDomainModule {
|
|||
fun providesGetNFTCollectionsUseCase(
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
nftRepository: NFTRepository,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
): GetNFTCollectionsUseCase = GetNFTCollectionsUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -60,10 +67,12 @@ internal object NFTDomainModule {
|
|||
@Singleton
|
||||
fun providesGetNFTAvailableNetworksUseCase(
|
||||
nftRepository: NFTRepository,
|
||||
singleAccountStatusListSupplier: SingleAccountStatusListSupplier,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
): GetNFTNetworksUseCase = GetNFTNetworksUseCase(
|
||||
currenciesRepository = currenciesRepository,
|
||||
nftRepository = nftRepository,
|
||||
singleAccountStatusListSupplier = singleAccountStatusListSupplier,
|
||||
)
|
||||
|
||||
@Provides
|
||||
|
|
@ -138,8 +147,15 @@ internal object NFTDomainModule {
|
|||
fun provideClearNFTCacheUseCase(
|
||||
nftRepository: NFTRepository,
|
||||
currenciesRepository: CurrenciesRepository,
|
||||
accountsFeatureToggles: AccountsFeatureToggles,
|
||||
singleAccountListSupplier: SingleAccountListSupplier,
|
||||
): ObserveAndClearNFTCacheIfNeedUseCase {
|
||||
return ObserveAndClearNFTCacheIfNeedUseCase(nftRepository, currenciesRepository)
|
||||
return ObserveAndClearNFTCacheIfNeedUseCase(
|
||||
nftRepository = nftRepository,
|
||||
currenciesRepository = currenciesRepository,
|
||||
accountsFeatureToggles = accountsFeatureToggles,
|
||||
singleAccountListSupplier = singleAccountListSupplier,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
|
|
|
|||
|
|
@ -0,0 +1,41 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.news.repository.NewsRepository
|
||||
import com.tangem.domain.news.usecase.GetNewsCategoriesUseCase
|
||||
import com.tangem.domain.news.usecase.GetNewsListBatchFlowUseCase
|
||||
import com.tangem.domain.news.usecase.ObserveNewsDetailsUseCase
|
||||
import com.tangem.domain.news.usecase.ManageTrendingNewsUseCase
|
||||
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 NewsDomainModule {
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNewsCategoriesUseCase(repository: NewsRepository): GetNewsCategoriesUseCase {
|
||||
return GetNewsCategoriesUseCase(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideObserveNewsDetailsUseCase(repository: NewsRepository): ObserveNewsDetailsUseCase {
|
||||
return ObserveNewsDetailsUseCase(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideObserveTrendingNewsUseCase(repository: NewsRepository): ManageTrendingNewsUseCase {
|
||||
return ManageTrendingNewsUseCase(repository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideGetNewsListBatchFlowUseCase(repository: NewsRepository): GetNewsListBatchFlowUseCase {
|
||||
return GetNewsListBatchFlowUseCase(repository)
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
@Singleton
|
||||
fun provideGetOnrampProviderWithQuoteUseCase(
|
||||
|
|
|
|||
|
|
@ -1,10 +1,12 @@
|
|||
package com.tangem.tap.di.domain
|
||||
|
||||
import com.tangem.domain.staking.*
|
||||
import com.tangem.domain.staking.repositories.StakingActionRepository
|
||||
import com.tangem.domain.staking.repositories.P2PEthPoolRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitActionRepository
|
||||
import com.tangem.domain.staking.repositories.StakingErrorResolver
|
||||
import com.tangem.domain.staking.repositories.StakeKitRepository
|
||||
import com.tangem.domain.staking.repositories.StakingRepository
|
||||
import com.tangem.domain.staking.repositories.StakingTransactionHashRepository
|
||||
import com.tangem.domain.staking.repositories.StakeKitTransactionHashRepository
|
||||
import com.tangem.domain.staking.single.SingleYieldBalanceFetcher
|
||||
import com.tangem.domain.staking.toggles.StakingFeatureToggles
|
||||
import com.tangem.domain.staking.usecase.StakingApyFlowUseCase
|
||||
|
|
@ -17,6 +19,7 @@ import javax.inject.Singleton
|
|||
|
||||
@Module
|
||||
@InstallIn(SingletonComponent::class)
|
||||
@Suppress("TooManyFunctions")
|
||||
internal object StakingDomainModule {
|
||||
|
||||
@Provides
|
||||
|
|
@ -34,11 +37,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetStakingEntryInfoUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): GetStakingEntryInfoUseCase {
|
||||
return GetStakingEntryInfoUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -46,11 +49,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetYieldUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): GetYieldUseCase {
|
||||
return GetYieldUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -58,13 +61,13 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchActionsUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakingActionRepository: StakingActionRepository,
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakeKitActionRepository: StakeKitActionRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): FetchActionsUseCase {
|
||||
return FetchActionsUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakingActionRepository = stakingActionRepository,
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
stakeKitActionRepository = stakeKitActionRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -72,11 +75,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetActionsUseCase(
|
||||
stakingActionRepository: StakingActionRepository,
|
||||
stakeKitActionRepository: StakeKitActionRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): GetActionsUseCase {
|
||||
return GetActionsUseCase(
|
||||
stakingActionRepository = stakingActionRepository,
|
||||
stakeKitActionRepository = stakeKitActionRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -84,11 +87,25 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetStakingTokensUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): FetchStakingTokensUseCase {
|
||||
return FetchStakingTokensUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideFetchStakingOptionsUseCase(
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
p2pRepository: P2PEthPoolRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): FetchStakingOptionsUseCase {
|
||||
return FetchStakingOptionsUseCase(
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
p2pRepository = p2pRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -108,11 +125,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetStakingTransactionsUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): GetStakingTransactionsUseCase {
|
||||
return GetStakingTransactionsUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -120,11 +137,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGasEstimateUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): EstimateGasUseCase {
|
||||
return EstimateGasUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -132,11 +149,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideSubmitHashUseCase(
|
||||
stakingTransactionHashRepository: StakingTransactionHashRepository,
|
||||
stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): SubmitHashUseCase {
|
||||
return SubmitHashUseCase(
|
||||
stakingTransactionHashRepository = stakingTransactionHashRepository,
|
||||
stakeKitTransactionHashRepository = stakeKitTransactionHashRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -144,11 +161,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideSaveUnsubmittedHashUseCase(
|
||||
stakingTransactionHashRepository: StakingTransactionHashRepository,
|
||||
stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): SaveUnsubmittedHashUseCase {
|
||||
return SaveUnsubmittedHashUseCase(
|
||||
stakingTransactionHashRepository = stakingTransactionHashRepository,
|
||||
stakeKitTransactionHashRepository = stakeKitTransactionHashRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -166,11 +183,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideSendUnsubmittedHashesUseCase(
|
||||
stakingTransactionHashRepository: StakingTransactionHashRepository,
|
||||
stakeKitTransactionHashRepository: StakeKitTransactionHashRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): SendUnsubmittedHashesUseCase {
|
||||
return SendUnsubmittedHashesUseCase(
|
||||
stakingTransactionHashRepository = stakingTransactionHashRepository,
|
||||
stakeKitTransactionHashRepository = stakeKitTransactionHashRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -178,11 +195,11 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideGetConstructedStakingTransactionUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakingErrorResolver: StakingErrorResolver,
|
||||
): GetConstructedStakingTransactionUseCase {
|
||||
return GetConstructedStakingTransactionUseCase(
|
||||
stakingRepository = stakingRepository,
|
||||
stakeKitRepository = stakeKitRepository,
|
||||
stakingErrorResolver = stakingErrorResolver,
|
||||
)
|
||||
}
|
||||
|
|
@ -222,9 +239,9 @@ internal object StakingDomainModule {
|
|||
@Provides
|
||||
@Singleton
|
||||
fun provideStakingApyFlowUseCase(
|
||||
stakingRepository: StakingRepository,
|
||||
stakeKitRepository: StakeKitRepository,
|
||||
stakingFeatureToggles: StakingFeatureToggles,
|
||||
): StakingApyFlowUseCase {
|
||||
return StakingApyFlowUseCase(stakingRepository, stakingFeatureToggles)
|
||||
return StakingApyFlowUseCase(stakeKitRepository, stakingFeatureToggles)
|
||||
}
|
||||
}
|
||||
|
|
@ -20,10 +20,7 @@ import com.tangem.domain.staking.single.SingleYieldBalanceSupplier
|
|||
import com.tangem.domain.tokens.*
|
||||
import com.tangem.domain.tokens.operations.BaseCurrencyStatusOperations
|
||||
import com.tangem.domain.tokens.operations.CachedCurrenciesStatusesOperations
|
||||
import com.tangem.domain.tokens.repository.CurrenciesRepository
|
||||
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.repository.*
|
||||
import com.tangem.domain.tokens.wallet.WalletBalanceFetcher
|
||||
import com.tangem.domain.walletmanager.WalletManagersFacade
|
||||
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
|
||||
@Singleton
|
||||
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
|
||||
@Singleton
|
||||
fun provideGetCryptoCurrencyUseCase(
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ internal object TransactionDomainModule {
|
|||
fun provideGetFeeUseCase(walletManagersFacade: WalletManagersFacade): GetFeeUseCase {
|
||||
return GetFeeUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
demoConfig = DemoConfig(),
|
||||
demoConfig = DemoConfig,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -52,7 +52,7 @@ internal object TransactionDomainModule {
|
|||
dispatchers: CoroutineDispatcherProvider,
|
||||
): SendTransactionUseCase {
|
||||
return SendTransactionUseCase(
|
||||
demoConfig = DemoConfig(),
|
||||
demoConfig = DemoConfig,
|
||||
cardSdkConfigRepository = cardSdkConfigRepository,
|
||||
transactionRepository = transactionRepository,
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
|
|
@ -131,7 +131,7 @@ internal object TransactionDomainModule {
|
|||
fun provideEstimateFeeUseCase(walletManagersFacade: WalletManagersFacade): EstimateFeeUseCase {
|
||||
return EstimateFeeUseCase(
|
||||
walletManagersFacade = walletManagersFacade,
|
||||
demoConfig = DemoConfig(),
|
||||
demoConfig = DemoConfig,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -166,10 +166,34 @@ internal object WalletsDomainModule {
|
|||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUnlockWalletUseCase(userWalletsListManager: UserWalletsListManager): UnlockWalletsUseCase {
|
||||
fun providesUnlockWalletsUseCase(userWalletsListManager: UserWalletsListManager): UnlockWalletsUseCase {
|
||||
return UnlockWalletsUseCase(userWalletsListManager = userWalletsListManager)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesUnlockWalletUseCase(
|
||||
nonBiometricUnlockWalletUseCase: NonBiometricUnlockWalletUseCase,
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
): UnlockWalletUseCase {
|
||||
return UnlockWalletUseCase(
|
||||
nonBiometricUnlockWalletUseCase = nonBiometricUnlockWalletUseCase,
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesNonBiometricUnlockWalletUseCase(
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
walletsRepository: WalletsRepository,
|
||||
): NonBiometricUnlockWalletUseCase {
|
||||
return NonBiometricUnlockWalletUseCase(
|
||||
userWalletsListRepository = userWalletsListRepository,
|
||||
walletsRepository = walletsRepository,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun providesSelectWalletUseCase(
|
||||
|
|
@ -483,4 +507,18 @@ internal object WalletsDomainModule {
|
|||
dispatchers = dispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideHasSecuredWalletsUseCase(
|
||||
userWalletsListRepository: UserWalletsListRepository,
|
||||
): HasSecuredWalletsUseCase {
|
||||
return HasSecuredWalletsUseCase(userWalletsListRepository = userWalletsListRepository)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideSyncWalletWithRemoteUseCase(walletsRepository: WalletsRepository): SyncWalletWithRemoteUseCase {
|
||||
return SyncWalletWithRemoteUseCase(walletsRepository = walletsRepository)
|
||||
}
|
||||
}
|
||||
|
|
@ -189,4 +189,14 @@ internal object YieldSupplyDomainModule {
|
|||
dispatcherProvider = dispatcherProvider,
|
||||
)
|
||||
}
|
||||
|
||||
@Provides
|
||||
@Singleton
|
||||
fun provideYieldSupplyEnterStatusUseCase(
|
||||
yieldSupplyRepository: YieldSupplyRepository,
|
||||
): YieldSupplyEnterStatusUseCase {
|
||||
return YieldSupplyEnterStatusUseCase(
|
||||
yieldSupplyRepository = yieldSupplyRepository,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -2,11 +2,11 @@ package com.tangem.tap.domain
|
|||
|
||||
import com.tangem.blockchain.common.Token
|
||||
import com.tangem.blockchain.common.Wallet
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.common.extensions.withMainContext
|
||||
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.proxy.redux.DaggerGraphState
|
||||
import com.tangem.tap.store
|
||||
import com.tangem.tap.tangemSdkManager
|
||||
import com.tangem.utils.coroutines.AppCoroutineDispatcherProvider
|
||||
|
|
@ -30,11 +30,12 @@ class TapWalletManager(
|
|||
// ensuring that only one job is active at any given time.
|
||||
loadUserWalletDataJob = CoroutineScope(dispatchers.io)
|
||||
.launch { loadUserWalletData(userWallet) }
|
||||
.also { it.join() }
|
||||
.apply { join() }
|
||||
}
|
||||
|
||||
private suspend fun loadUserWalletData(userWallet: UserWallet) {
|
||||
Analytics.setContext(userWallet)
|
||||
val trackingContextProxy = store.inject(DaggerGraphState::trackingContextProxy)
|
||||
trackingContextProxy.setContext(userWallet)
|
||||
|
||||
if (userWallet is UserWallet.Cold) {
|
||||
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.Basic
|
||||
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.ui.UiMessageSender
|
||||
import com.tangem.core.ui.R
|
||||
|
|
@ -47,6 +48,7 @@ import javax.inject.Singleton
|
|||
internal class LegacyScanProcessor @Inject constructor(
|
||||
@GlobalUiMessageSender private val uiMessageSender: UiMessageSender,
|
||||
private val analyticsEventHandler: AnalyticsEventHandler,
|
||||
private val trackingContextProxy: TrackingContextProxy,
|
||||
) {
|
||||
|
||||
suspend fun scan(
|
||||
|
|
@ -118,16 +120,14 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
}
|
||||
}
|
||||
|
||||
private fun sendAnalytics(analyticsEvent: AnalyticsEvent?, scanResponse: ScanResponse) {
|
||||
analyticsEvent?.let { event ->
|
||||
// this workaround needed to send CardWasScannedEvent without adding a context
|
||||
val interceptor = CardContextInterceptor(scanResponse)
|
||||
val params = event.params.toMutableMap()
|
||||
interceptor.intercept(params)
|
||||
event.params = params.toMap()
|
||||
private fun sendAnalytics(analyticsEvent: AnalyticsEvent, scanResponse: ScanResponse) {
|
||||
// this workaround needed to send CardWasScannedEvent without adding a context
|
||||
val interceptor = CardContextInterceptor(scanResponse)
|
||||
val params = analyticsEvent.params.toMutableMap()
|
||||
interceptor.intercept(params)
|
||||
analyticsEvent.params = params.toMap()
|
||||
|
||||
Analytics.send(event)
|
||||
}
|
||||
Analytics.send(analyticsEvent)
|
||||
}
|
||||
|
||||
// TODO: [REDACTED_JIRA]
|
||||
|
|
@ -211,7 +211,7 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
},
|
||||
) {
|
||||
if (OnboardingHelper.isOnboardingCase(scanResponse)) {
|
||||
Analytics.addContext(scanResponse)
|
||||
trackingContextProxy.addContext(scanResponse)
|
||||
onWalletNotCreated()
|
||||
navigateTo(
|
||||
AppRoute.Onboarding(
|
||||
|
|
@ -220,7 +220,7 @@ internal class LegacyScanProcessor @Inject constructor(
|
|||
),
|
||||
) { onProgressStateChange(it) }
|
||||
} else {
|
||||
Analytics.setContext(scanResponse)
|
||||
trackingContextProxy.setContext(scanResponse)
|
||||
|
||||
val wasTwinsOnboardingShown =
|
||||
store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase).invokeSync()
|
||||
|
|
|
|||
|
|
@ -3,15 +3,12 @@ package com.tangem.tap.domain.scanCard.chains
|
|||
import arrow.core.left
|
||||
import arrow.core.right
|
||||
import com.tangem.common.routing.AppRoute
|
||||
import com.tangem.core.analytics.Analytics
|
||||
import com.tangem.domain.card.ScanCardException
|
||||
import com.tangem.domain.card.common.util.twinsIsTwinned
|
||||
import com.tangem.domain.core.chain.Chain
|
||||
import com.tangem.domain.core.chain.ResultChain
|
||||
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.setContext
|
||||
import com.tangem.tap.common.redux.AppState
|
||||
import com.tangem.tap.features.onboarding.OnboardingHelper
|
||||
import com.tangem.tap.proxy.redux.DaggerGraphState
|
||||
|
|
@ -34,9 +31,11 @@ class CheckForOnboardingChain(
|
|||
) : ResultChain<ScanCardException, ScanResponse>() {
|
||||
|
||||
override suspend fun launch(previousChainResult: ScanResponse): ScanChainResult {
|
||||
val trackingContextProxy = store.inject(DaggerGraphState::trackingContextProxy)
|
||||
|
||||
return when {
|
||||
OnboardingHelper.isOnboardingCase(previousChainResult) -> {
|
||||
Analytics.addContext(previousChainResult)
|
||||
trackingContextProxy.addContext(previousChainResult)
|
||||
ScanChainException.OnboardingNeeded(
|
||||
AppRoute.Onboarding(
|
||||
scanResponse = previousChainResult,
|
||||
|
|
@ -45,7 +44,7 @@ class CheckForOnboardingChain(
|
|||
).left()
|
||||
}
|
||||
else -> {
|
||||
Analytics.setContext(previousChainResult)
|
||||
trackingContextProxy.setContext(previousChainResult)
|
||||
|
||||
val wasTwinsOnboardingShown = store.inject(DaggerGraphState::wasTwinsOnboardingShownUseCase)
|
||||
.invokeSync()
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import com.tangem.common.authentication.AuthenticationManager
|
|||
import com.tangem.common.authentication.keystore.KeystoreManager
|
||||
import com.tangem.common.core.*
|
||||
import com.tangem.common.extensions.ByteArrayKey
|
||||
import com.tangem.common.extensions.hexToBytes
|
||||
import com.tangem.common.services.secure.SecureStorage
|
||||
import com.tangem.common.usersCode.UserCodeRepository
|
||||
import com.tangem.core.analytics.Analytics
|
||||
|
|
@ -18,13 +19,13 @@ import com.tangem.core.res.getStringSafe
|
|||
import com.tangem.crypto.bip39.DefaultMnemonic
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
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.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.domain.card.repository.CardSdkConfigRepository
|
||||
import com.tangem.domain.models.scan.CardDTO
|
||||
import com.tangem.domain.models.scan.ScanResponse
|
||||
import com.tangem.domain.models.wallet.UserWalletId
|
||||
import com.tangem.domain.visa.model.*
|
||||
import com.tangem.domain.wallets.derivations.derivationStyleProvider
|
||||
import com.tangem.features.onboarding.v2.OnboardingV2FeatureToggles
|
||||
import com.tangem.operations.ScanTask
|
||||
import com.tangem.operations.derivation.DerivationTaskResponse
|
||||
|
|
@ -44,6 +45,8 @@ import com.tangem.tap.domain.tasks.product.CreateProductWalletTask
|
|||
import com.tangem.tap.domain.tasks.product.ResetBackupCardTask
|
||||
import com.tangem.tap.domain.tasks.product.ResetToFactorySettingsTask
|
||||
import com.tangem.tap.domain.tasks.product.ScanProductTask
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPayGenerateAddressAndSignChallengeTask
|
||||
import com.tangem.tap.domain.tasks.visa.TangemPaySignWithdrawalHashTask
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCardActivationTask
|
||||
import com.tangem.tap.domain.tasks.visa.VisaCustomerWalletApproveTask
|
||||
import com.tangem.tap.domain.twins.CreateFirstTwinWalletTask
|
||||
|
|
@ -62,6 +65,7 @@ internal class DefaultTangemSdkManager(
|
|||
private val resources: Resources,
|
||||
private val visaCardScanHandler: VisaCardScanHandler,
|
||||
private val visaCardActivationTaskFactory: VisaCardActivationTask.Factory,
|
||||
private val tangemPayChallengeTaskFactory: TangemPayGenerateAddressAndSignChallengeTask.Factory,
|
||||
private val onboardingV2FeatureToggles: OnboardingV2FeatureToggles,
|
||||
) : TangemSdkManager {
|
||||
|
||||
|
|
@ -76,13 +80,12 @@ internal class DefaultTangemSdkManager(
|
|||
secureStorage = tangemSdk.secureStorage,
|
||||
)
|
||||
}
|
||||
override val needEnrollBiometrics: Boolean
|
||||
get() = tangemSdk.authenticationManager.needEnrollBiometrics
|
||||
|
||||
override val canUseBiometry: Boolean
|
||||
get() = tangemSdk.authenticationManager.canAuthenticate || needEnrollBiometrics
|
||||
|
||||
override val needEnrollBiometrics: Boolean
|
||||
get() = tangemSdk.authenticationManager.needEnrollBiometrics
|
||||
|
||||
override val keystoreManager: KeystoreManager
|
||||
get() = tangemSdk.keystoreManager
|
||||
|
||||
|
|
@ -511,6 +514,27 @@ 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)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult<String> {
|
||||
return coroutineScope {
|
||||
runTaskAsyncReturnOnMain(
|
||||
runnable = TangemPaySignWithdrawalHashTask(cardId = cardId, hash = hash.hexToBytes()),
|
||||
cardId = cardId,
|
||||
initialMessage = Message(resources.getStringSafe(R.string.initial_message_tap_header)),
|
||||
)
|
||||
}
|
||||
}
|
||||
// endregion
|
||||
|
||||
companion object {
|
||||
|
|
|
|||
|
|
@ -213,5 +213,15 @@ class MockTangemSdkManager(
|
|||
error("Not implemented")
|
||||
}
|
||||
|
||||
override suspend fun tangemPayProduceInitialCredentials(
|
||||
cardId: String,
|
||||
): CompletionResult<TangemPayInitialCredentials> {
|
||||
error("Not implemented")
|
||||
}
|
||||
|
||||
override suspend fun getWithdrawalSignature(cardId: String, hash: String): CompletionResult<String> {
|
||||
error("Not implemented")
|
||||
}
|
||||
|
||||
// endregion
|
||||
}
|
||||
|
|
@ -1,7 +1,11 @@
|
|||
package com.tangem.tap.domain.sdk.mocks.content
|
||||
|
||||
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.crypto.hdWallet.DerivationPath
|
||||
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),
|
||||
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(
|
||||
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'/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(
|
||||
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),
|
||||
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(
|
||||
|
|
@ -244,14 +270,49 @@ object WalletMockContent : MockContent {
|
|||
ExtendedPublicKeysMap(
|
||||
mapOf(
|
||||
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),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
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),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
|
|
@ -296,9 +357,16 @@ object WalletMockContent : MockContent {
|
|||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
DerivationPath("m/44'/60'/0'/0/0") to ExtendedPublicKey( // xrp
|
||||
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),
|
||||
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),
|
||||
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,
|
||||
|
|
@ -312,19 +380,54 @@ object WalletMockContent : MockContent {
|
|||
ExtendedPublicKeysMap(
|
||||
mapOf(
|
||||
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),
|
||||
depth = 0,
|
||||
parentFingerprint = byteArrayOf(0, 0, 0, 0),
|
||||
childNumber = 0,
|
||||
),
|
||||
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, -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.hdWallet.DerivationPath
|
||||
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.configs.CardConfig
|
||||
import com.tangem.domain.demo.models.DemoConfig
|
||||
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.StartPrimaryCardLinkingCommand
|
||||
import com.tangem.operations.derivation.DeriveMultipleWalletPublicKeysTask
|
||||
|
|
@ -60,7 +61,13 @@ class CreateProductWalletTask(
|
|||
val cardDto = CardDTO(card)
|
||||
|
||||
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() ->
|
||||
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> {
|
||||
return when {
|
||||
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.demoBlockchains.toList()
|
||||
DemoHelper.isDemoCardId(cardId) -> DemoHelper.config.getDemoBlockchains(cardId).toList()
|
||||
card.isTestCard -> listOf(Blockchain.BitcoinTestnet, Blockchain.EthereumTestnet)
|
||||
else -> listOf(Blockchain.Bitcoin, Blockchain.Ethereum)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -38,7 +38,7 @@ internal class DerivationsFinder(
|
|||
getBlockchains(userWalletId)
|
||||
}.ifEmpty {
|
||||
if (DemoHelper.isDemoCardId(card.cardId)) {
|
||||
getDemoBlockchains(derivationStyle)
|
||||
getDemoBlockchains(derivationStyle, card.cardId)
|
||||
} else {
|
||||
getDefaultBlockchains(derivationStyle)
|
||||
}
|
||||
|
|
@ -77,8 +77,8 @@ internal class DerivationsFinder(
|
|||
}
|
||||
|
||||
// TODO: Move to user wallet config
|
||||
private fun getDemoBlockchains(derivationStyle: DerivationStyle?): MutableSet<BlockchainToDerive> {
|
||||
return DemoHelper.config.demoBlockchains.mapToBlockchainsWithDerivations(derivationStyle)
|
||||
private fun getDemoBlockchains(derivationStyle: DerivationStyle?, cardId: String): MutableSet<BlockchainToDerive> {
|
||||
return DemoHelper.config.getDemoBlockchains(cardId).mapToBlockchainsWithDerivations(derivationStyle)
|
||||
}
|
||||
|
||||
// TODO: Move to user wallet config
|
||||
|
|
|
|||
|
|
@ -17,7 +17,6 @@ import com.tangem.tap.domain.tasks.UserWalletIdPreflightReadFilter
|
|||
*
|
||||
[REDACTED_AUTHOR]
|
||||
*/
|
||||
// TODO remove it after test after resolve [REDACTED_JIRA]
|
||||
internal class ResetBackupCardTask(
|
||||
private val userWalletId: UserWalletId,
|
||||
) : CardSessionRunnable<Boolean> {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,134 @@
|
|||
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.domain.wallets.builder.UserWalletIdBuilder
|
||||
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 address = when (val derivationResult = runDerivationTask(session, wallet)) {
|
||||
is CompletionResult.Failure<*> -> return CompletionResult.Failure(derivationResult.error)
|
||||
is CompletionResult.Success<ExtendedPublicKey> -> generateAddressFromExtendedKey(derivationResult.data)
|
||||
}
|
||||
|
||||
val userWalletId = UserWalletIdBuilder.walletPublicKey(wallet.publicKey)
|
||||
val challenge = withContext(dispatchersProvider.io) {
|
||||
visaAuthRemoteDataSource.getCustomerWalletAuthChallenge(
|
||||
customerWalletAddress = address,
|
||||
customerWalletId = userWalletId.stringValue,
|
||||
)
|
||||
}.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
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,102 @@
|
|||
package com.tangem.tap.domain.tasks.visa
|
||||
|
||||
import com.tangem.blockchain.common.UnmarshalHelper
|
||||
import com.tangem.common.CompletionResult
|
||||
import com.tangem.common.card.Card
|
||||
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.common.extensions.toDecompressedPublicKey
|
||||
import com.tangem.common.extensions.toHexString
|
||||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
import com.tangem.crypto.hdWallet.bip32.ExtendedPublicKey
|
||||
import com.tangem.domain.card.common.visa.VisaUtilities
|
||||
import com.tangem.domain.visa.error.VisaActivationError
|
||||
import com.tangem.operations.derivation.DeriveWalletPublicKeyTask
|
||||
import com.tangem.operations.sign.SignHashCommand
|
||||
|
||||
class TangemPaySignWithdrawalHashTask(
|
||||
private val cardId: String,
|
||||
private val hash: ByteArray,
|
||||
) : CardSessionRunnable<String> {
|
||||
|
||||
override fun run(session: CardSession, callback: CompletionCallback<String>) {
|
||||
val card = session.environment.card ?: run {
|
||||
callback(CompletionResult.Failure(TangemSdkError.MissingPreflightRead()))
|
||||
return
|
||||
}
|
||||
|
||||
if (card.cardId != cardId) {
|
||||
callback(CompletionResult.Failure(VisaActivationError.CardIdNotMatched.tangemError))
|
||||
return
|
||||
}
|
||||
|
||||
proceedSign(card, session, callback)
|
||||
}
|
||||
|
||||
private fun proceedSign(card: Card, session: CardSession, callback: CompletionCallback<String>) {
|
||||
val derivationPath = VisaUtilities.customDerivationPath
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
||||
return
|
||||
}
|
||||
|
||||
val derivationTask = DeriveWalletPublicKeyTask(
|
||||
walletPublicKey = wallet.publicKey,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
||||
derivationTask.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
signData(
|
||||
targetWalletPublicKey = wallet.publicKey,
|
||||
derivationPath = derivationPath,
|
||||
session = session,
|
||||
extendedPublicKey = result.data,
|
||||
callback = callback,
|
||||
)
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun signData(
|
||||
targetWalletPublicKey: ByteArray,
|
||||
derivationPath: DerivationPath?,
|
||||
extendedPublicKey: ExtendedPublicKey?,
|
||||
session: CardSession,
|
||||
callback: CompletionCallback<String>,
|
||||
) {
|
||||
val signTask = SignHashCommand(
|
||||
hash = hash,
|
||||
walletPublicKey = targetWalletPublicKey,
|
||||
derivationPath = derivationPath,
|
||||
)
|
||||
|
||||
signTask.run(session) { result ->
|
||||
when (result) {
|
||||
is CompletionResult.Success -> {
|
||||
val rsvSignature = UnmarshalHelper.unmarshalSignatureExtended(
|
||||
signature = result.data.signature,
|
||||
hash = hash,
|
||||
publicKey = extendedPublicKey?.publicKey?.toDecompressedPublicKey()
|
||||
?: targetWalletPublicKey.toDecompressedPublicKey(),
|
||||
).asRSVLegacyEVM().toHexString().lowercase()
|
||||
|
||||
callback(CompletionResult.Success(rsvSignature))
|
||||
}
|
||||
is CompletionResult.Failure -> {
|
||||
callback(CompletionResult.Failure(result.error))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -177,7 +177,7 @@ class VisaCardActivationTask @AssistedInject constructor(
|
|||
.getOrElse { raise(it.tangemError) }
|
||||
|
||||
if (remoteState !is VisaActivationRemoteState.CardWalletSignatureRequired) {
|
||||
return raise(VisaActivationError.WrongRemoteState.tangemError)
|
||||
raise(VisaActivationError.WrongRemoteState.tangemError)
|
||||
}
|
||||
|
||||
visaActivationRepository.getCardWalletAcceptanceData(
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@ import com.tangem.common.extensions.toHexString
|
|||
import com.tangem.core.error.ext.tangemError
|
||||
import com.tangem.crypto.hdWallet.DerivationPath
|
||||
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.VisaWalletPublicKeyUtility
|
||||
import com.tangem.domain.card.common.visa.VisaWalletPublicKeyUtility.findKeyWithoutDerivation
|
||||
|
|
@ -59,21 +58,7 @@ class VisaCustomerWalletApproveTask(
|
|||
session: CardSession,
|
||||
callback: CompletionCallback<VisaSignedDataByCustomerWallet>,
|
||||
) {
|
||||
val cardDTO = CardDTO(card)
|
||||
|
||||
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 derivationPath = VisaUtilities.customDerivationPath
|
||||
|
||||
val wallet = card.wallets.firstOrNull { it.curve == EllipticCurve.Secp256k1 } ?: run {
|
||||
callback(CompletionResult.Failure(VisaActivationError.MissingWallet.tangemError))
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ import com.tangem.domain.visa.model.VisaCardActivationStatus
|
|||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.domain.wallets.legacy.UserWalletsListManager
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.sdk.storage.AndroidSecureStorage
|
||||
import com.tangem.sdk.storage.AndroidSecureStorageV2
|
||||
import com.tangem.sdk.storage.createEncryptedSharedPreferences
|
||||
|
|
@ -111,6 +112,7 @@ internal object UserWalletsListManagerModule {
|
|||
publicInformationRepository = publicInformationRepository,
|
||||
sensitiveInformationRepository = sensitiveInformationRepository,
|
||||
selectedUserWalletRepository = selectedUserWalletRepository,
|
||||
dispatcherProvider = dispatchers,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
@ -122,6 +124,7 @@ internal object UserWalletsListManagerModule {
|
|||
passwordRequester: HotWalletPasswordRequester,
|
||||
appPreferencesStore: AppPreferencesStore,
|
||||
hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||
tangemHotSdk: TangemHotSdk,
|
||||
): UserWalletsListRepository {
|
||||
val moshi = buildMoshi()
|
||||
val secureStorage = buildSecureStorage(applicationContext = applicationContext)
|
||||
|
|
@ -168,6 +171,7 @@ internal object UserWalletsListManagerModule {
|
|||
appPreferencesStore = appPreferencesStore,
|
||||
savePersistentInformation = ProviderSuspend { true }, // Always save persistent information for now
|
||||
hotWalletAccessCodeAttemptsRepository = hotWalletAccessCodeAttemptsRepository,
|
||||
tangemHotSdk = tangemHotSdk,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -16,19 +16,29 @@ import com.tangem.tap.domain.userWalletList.utils.encryptionKey
|
|||
import com.tangem.tap.domain.userWalletList.utils.lockAll
|
||||
import com.tangem.tap.domain.userWalletList.utils.toUserWallets
|
||||
import com.tangem.tap.domain.userWalletList.utils.updateWith
|
||||
import com.tangem.utils.coroutines.CoroutineDispatcherProvider
|
||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
||||
import kotlinx.coroutines.flow.*
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Mutex
|
||||
import kotlinx.coroutines.sync.withLock
|
||||
import kotlinx.coroutines.withContext
|
||||
import timber.log.Timber
|
||||
|
||||
@Suppress("LargeClass")
|
||||
@OptIn(ExperimentalCoroutinesApi::class)
|
||||
internal class BiometricUserWalletsListManager(
|
||||
private val keysRepository: UserWalletsKeysRepository,
|
||||
private val publicInformationRepository: UserWalletsPublicInformationRepository,
|
||||
private val sensitiveInformationRepository: UserWalletsSensitiveInformationRepository,
|
||||
private val selectedUserWalletRepository: SelectedUserWalletRepository,
|
||||
private val dispatcherProvider: CoroutineDispatcherProvider,
|
||||
) : UserWalletsListManager.Lockable {
|
||||
private val state = MutableStateFlow(State())
|
||||
|
||||
private var hasSavedWallets: Boolean? = null
|
||||
private val savedWalletMutex = Mutex()
|
||||
|
||||
override val isLockable: Boolean = true
|
||||
|
||||
override val userWallets: Flow<List<UserWallet>>
|
||||
|
|
@ -36,11 +46,6 @@ internal class BiometricUserWalletsListManager(
|
|||
.mapLatest { it.userWallets }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val userWalletsSync: List<UserWallet>
|
||||
get() = state.value.userWallets
|
||||
|
||||
|
|
@ -66,29 +71,52 @@ internal class BiometricUserWalletsListManager(
|
|||
get() = state.value.isLocked
|
||||
|
||||
override val hasUserWallets: Boolean
|
||||
get() = keysRepository.hasSavedEncryptionKeys()
|
||||
get() {
|
||||
return runBlocking {
|
||||
// workaround to avoid calling hasSavedEncryptionKeys many times because of performance
|
||||
savedWalletMutex.withLock {
|
||||
Timber.i("Checking if user has saved wallets")
|
||||
val hasSavedWalletsLocal = hasSavedWallets
|
||||
if (hasSavedWalletsLocal == null || !hasSavedWalletsLocal) {
|
||||
val hasKeys = keysRepository.hasSavedEncryptionKeys()
|
||||
hasSavedWallets = hasKeys
|
||||
hasKeys
|
||||
} else {
|
||||
Timber.i("User has saved wallets (from cache)")
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override val walletsCount: Int
|
||||
get() = state.value.userWallets.size
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override suspend fun unlock(type: UnlockType): CompletionResult<UserWallet> {
|
||||
return unlockAndSetSelectedUserWallet(type)
|
||||
.mapFailure { error ->
|
||||
Timber.e(error, "Unable to unlock user wallets")
|
||||
if (error is UserWalletsListError) {
|
||||
error
|
||||
} else {
|
||||
UserWalletsListError.UnableToUnlockUserWallets(error)
|
||||
return withContext(dispatcherProvider.io) {
|
||||
unlockAndSetSelectedUserWallet(type)
|
||||
.mapFailure { error ->
|
||||
Timber.e(error, "Unable to unlock user wallets")
|
||||
if (error is UserWalletsListError) {
|
||||
error
|
||||
} else {
|
||||
UserWalletsListError.UnableToUnlockUserWallets(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
.map { selectedUserWallet ->
|
||||
if (selectedUserWallet == null || selectedUserWallet.isLocked) {
|
||||
Timber.e("Unable to find selected user wallet")
|
||||
throw UserWalletsListError.NoUserWalletSelected
|
||||
} else {
|
||||
selectedUserWallet
|
||||
.map { selectedUserWallet ->
|
||||
if (selectedUserWallet == null || selectedUserWallet.isLocked) {
|
||||
Timber.e("Unable to find selected user wallet")
|
||||
throw UserWalletsListError.NoUserWalletSelected
|
||||
} else {
|
||||
selectedUserWallet
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun lock() {
|
||||
|
|
@ -120,18 +148,20 @@ internal class BiometricUserWalletsListManager(
|
|||
}
|
||||
|
||||
override suspend fun save(userWallet: UserWallet, canOverride: Boolean): CompletionResult<Unit> {
|
||||
return if (canOverride) {
|
||||
saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false)
|
||||
} else {
|
||||
val isWalletSaved = state.value.userWallets
|
||||
.any {
|
||||
it.walletId == userWallet.walletId
|
||||
}
|
||||
|
||||
if (isWalletSaved) {
|
||||
CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved)
|
||||
} else {
|
||||
return withContext(dispatcherProvider.io) {
|
||||
if (canOverride) {
|
||||
saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false)
|
||||
} else {
|
||||
val isWalletSaved = state.value.userWallets
|
||||
.any {
|
||||
it.walletId == userWallet.walletId
|
||||
}
|
||||
|
||||
if (isWalletSaved) {
|
||||
CompletionResult.Failure(UserWalletsListError.WalletAlreadySaved)
|
||||
} else {
|
||||
saveInternal(userWallet, changeSelectedUserWallet = true, canOverridePublicInfo = false)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -140,16 +170,18 @@ internal class BiometricUserWalletsListManager(
|
|||
userWalletId: UserWalletId,
|
||||
update: suspend (UserWallet) -> UserWallet,
|
||||
): CompletionResult<UserWallet> {
|
||||
return get(userWalletId)
|
||||
.map { storedUserWallet ->
|
||||
update(storedUserWallet)
|
||||
}
|
||||
.flatMap { updatedUserWallet ->
|
||||
saveInternal(updatedUserWallet, changeSelectedUserWallet = false, canOverridePublicInfo = true)
|
||||
}
|
||||
.flatMap {
|
||||
get(userWalletId)
|
||||
}
|
||||
return withContext(dispatcherProvider.io) {
|
||||
get(userWalletId)
|
||||
.map { storedUserWallet ->
|
||||
update(storedUserWallet)
|
||||
}
|
||||
.flatMap { updatedUserWallet ->
|
||||
saveInternal(updatedUserWallet, changeSelectedUserWallet = false, canOverridePublicInfo = true)
|
||||
}
|
||||
.flatMap {
|
||||
get(userWalletId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun delete(userWalletIds: List<UserWalletId>): CompletionResult<Unit> {
|
||||
|
|
@ -166,41 +198,48 @@ internal class BiometricUserWalletsListManager(
|
|||
return clear()
|
||||
}
|
||||
|
||||
return sensitiveInformationRepository.delete(idsToRemove)
|
||||
.flatMap { publicInformationRepository.delete(idsToRemove) }
|
||||
.map { keysRepository.delete(idsToRemove) }
|
||||
.map {
|
||||
state.update { prevState ->
|
||||
val remainingWallets = prevState.userWallets.filter { it.walletId !in idsToRemove }
|
||||
return withContext(dispatcherProvider.io) {
|
||||
sensitiveInformationRepository.delete(idsToRemove)
|
||||
.flatMap { publicInformationRepository.delete(idsToRemove) }
|
||||
.map { keysRepository.delete(idsToRemove) }
|
||||
.map {
|
||||
state.update { prevState ->
|
||||
val remainingWallets = prevState.userWallets.filter { it.walletId !in idsToRemove }
|
||||
|
||||
val isSelectedWalletDeleted = prevState.selectedUserWalletId in idsToRemove
|
||||
val newSelectedUserWallet = findOrSetSelectedWallet(
|
||||
prevSelectedWalletId = prevState.selectedUserWalletId,
|
||||
prevSelectedWalletIndex = prevState.userWallets.indexOfFirst {
|
||||
it.walletId == prevState.selectedUserWalletId
|
||||
},
|
||||
userWallets = remainingWallets,
|
||||
ignorePrevSelectedWallet = isSelectedWalletDeleted,
|
||||
)
|
||||
val isSelectedWalletDeleted = prevState.selectedUserWalletId in idsToRemove
|
||||
val newSelectedUserWallet = findOrSetSelectedWallet(
|
||||
prevSelectedWalletId = prevState.selectedUserWalletId,
|
||||
prevSelectedWalletIndex = prevState.userWallets.indexOfFirst {
|
||||
it.walletId == prevState.selectedUserWalletId
|
||||
},
|
||||
userWallets = remainingWallets,
|
||||
ignorePrevSelectedWallet = isSelectedWalletDeleted,
|
||||
)
|
||||
|
||||
prevState.copy(
|
||||
encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove },
|
||||
userWallets = remainingWallets,
|
||||
isLocked = remainingWallets.any { it.isLocked },
|
||||
selectedUserWalletId = newSelectedUserWallet?.walletId,
|
||||
)
|
||||
prevState.copy(
|
||||
encryptionKeys = prevState.encryptionKeys.filter { it.walletId !in idsToRemove },
|
||||
userWallets = remainingWallets,
|
||||
isLocked = remainingWallets.any { it.isLocked },
|
||||
selectedUserWalletId = newSelectedUserWallet?.walletId,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun clear(): CompletionResult<Unit> {
|
||||
return sensitiveInformationRepository.clear()
|
||||
.flatMap { publicInformationRepository.clear() }
|
||||
.map {
|
||||
keysRepository.clear()
|
||||
selectedUserWalletRepository.set(null)
|
||||
state.value = State()
|
||||
}
|
||||
savedWalletMutex.withLock {
|
||||
hasSavedWallets = null
|
||||
}
|
||||
return withContext(dispatcherProvider.io) {
|
||||
sensitiveInformationRepository.clear()
|
||||
.flatMap { publicInformationRepository.clear() }
|
||||
.map {
|
||||
keysRepository.clear()
|
||||
selectedUserWalletRepository.set(null)
|
||||
state.value = State()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun get(userWalletId: UserWalletId): CompletionResult<UserWallet> {
|
||||
|
|
|
|||
|
|
@ -20,11 +20,6 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
|||
.mapLatest { listOfNotNull(it.userWallet) }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val savedWalletsCount: Flow<Int>
|
||||
get() = state
|
||||
.mapLatest { walletsCount }
|
||||
.distinctUntilChanged()
|
||||
|
||||
override val selectedUserWallet: Flow<UserWallet>
|
||||
get() = state
|
||||
.mapLatest { it.userWallet }
|
||||
|
|
@ -46,6 +41,11 @@ internal class RuntimeUserWalletsListManager : UserWalletsListManager {
|
|||
override val walletsCount: Int
|
||||
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 {
|
||||
state.value.userWallet
|
||||
?.takeIf { it.walletId == userWalletId }
|
||||
|
|
|
|||
|
|
@ -2,9 +2,11 @@ package com.tangem.tap.domain.userWalletList.repository
|
|||
|
||||
import arrow.core.Either
|
||||
import arrow.core.left
|
||||
import arrow.core.raise.Raise
|
||||
import arrow.core.raise.either
|
||||
import arrow.core.right
|
||||
import com.tangem.common.*
|
||||
import com.tangem.common.core.TangemSdkError
|
||||
import com.tangem.datasource.local.preferences.AppPreferencesStore
|
||||
import com.tangem.datasource.local.preferences.PreferencesKeys
|
||||
import com.tangem.datasource.local.preferences.utils.getSyncOrDefault
|
||||
|
|
@ -18,6 +20,7 @@ import com.tangem.domain.wallets.R
|
|||
import com.tangem.domain.wallets.builder.UserWalletIdBuilder
|
||||
import com.tangem.domain.wallets.hot.HotWalletAccessCodeAttemptsRepository
|
||||
import com.tangem.domain.wallets.hot.HotWalletPasswordRequester
|
||||
import com.tangem.hot.sdk.TangemHotSdk
|
||||
import com.tangem.hot.sdk.model.HotWalletId
|
||||
import com.tangem.sdk.api.TangemSdkManager
|
||||
import com.tangem.tap.domain.userWalletList.model.UserWalletEncryptionKey
|
||||
|
|
@ -27,6 +30,8 @@ import com.tangem.tap.domain.userWalletList.utils.toUserWallets
|
|||
import com.tangem.tap.domain.userWalletList.utils.updateWith
|
||||
import com.tangem.utils.Provider
|
||||
import com.tangem.utils.ProviderSuspend
|
||||
import com.tangem.utils.coroutines.runSuspendCatching
|
||||
import com.tangem.utils.extensions.addOrReplace
|
||||
import com.tangem.utils.extensions.indexOfFirstOrNull
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.update
|
||||
|
|
@ -44,6 +49,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
private val savePersistentInformation: ProviderSuspend<Boolean>,
|
||||
private val appPreferencesStore: AppPreferencesStore,
|
||||
private val hotWalletAccessCodeAttemptsRepository: HotWalletAccessCodeAttemptsRepository,
|
||||
private val tangemHotSdk: TangemHotSdk,
|
||||
) : UserWalletsListRepository {
|
||||
|
||||
override val userWallets = MutableStateFlow<List<UserWallet>?>(null)
|
||||
|
|
@ -119,14 +125,19 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
}
|
||||
|
||||
val oldUserWallet = userWalletsSync().find { it.walletId == userWallet.walletId }
|
||||
|
||||
if (oldUserWallet != null) {
|
||||
checkForUpgradeAndDeleteHotWalletIfNeeded(
|
||||
newUserWallet = userWallet,
|
||||
oldUserWallet = oldUserWallet,
|
||||
)
|
||||
}
|
||||
|
||||
// update the userWallets state and add if it doesn't exist
|
||||
updateWallets { currentWallets ->
|
||||
val wallets = currentWallets.orEmpty()
|
||||
if (wallets.any { it.walletId == userWallet.walletId }) {
|
||||
wallets.map { if (it.walletId == userWallet.walletId) userWallet else it }
|
||||
} else {
|
||||
wallets + userWallet
|
||||
}
|
||||
wallets.addOrReplace(userWallet) { it.walletId == userWallet.walletId }
|
||||
}
|
||||
|
||||
// update the selectedUserWallet state if it is the only wallet
|
||||
|
|
@ -149,7 +160,7 @@ internal class DefaultUserWalletsListRepository(
|
|||
val encryptionKey = userWallet.encryptionKey
|
||||
?: raise(SetLockError.UserWalletLocked)
|
||||
|
||||
runCatching {
|
||||
runSuspendCatching {
|
||||
userWalletEncryptionKeysRepository.save(
|
||||
encryptionKey = UserWalletEncryptionKey(
|
||||
walletId = userWalletId,
|
||||
|
|
@ -193,6 +204,8 @@ internal class DefaultUserWalletsListRepository(
|
|||
|
||||
userWalletEncryptionKeysRepository.delete(userWalletIds)
|
||||
|
||||
removeHotWalletsFromSDK(userWalletIds)
|
||||
|
||||
userWallets.update { currentWallets ->
|
||||
val updatedWallets = currentWallets?.filter { userWalletIds.contains(it.walletId).not() }
|
||||
selectedUserWallet.update { currentSelected ->
|
||||
|
|
@ -226,16 +239,16 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
UserWalletsListRepository.UnlockMethod.AccessCode -> {
|
||||
if (userWallet !is UserWallet.Hot) {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
raise(UnlockWalletError.UnableToUnlock.Empty)
|
||||
}
|
||||
|
||||
val encryptionKey = requestPasswordRecursive(
|
||||
hotWalletId = userWallet.hotWalletId,
|
||||
block = { password ->
|
||||
runCatching {
|
||||
runSuspendCatching {
|
||||
userWalletEncryptionKeysRepository.getEncryptedWithPassword(userWalletId, password)
|
||||
}.onFailure {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}.onFailure { error ->
|
||||
raise(UnlockWalletError.UnableToUnlock.RawException(error))
|
||||
}.getOrNull()
|
||||
},
|
||||
biometryFallback = {
|
||||
|
|
@ -251,13 +264,11 @@ internal class DefaultUserWalletsListRepository(
|
|||
|
||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
|
||||
.doOnFailure { error ->
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
}
|
||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
||||
}
|
||||
is UserWalletsListRepository.UnlockMethod.Scan -> {
|
||||
if (userWallet !is UserWallet.Cold) {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
raise(UnlockWalletError.UnableToUnlock.Empty)
|
||||
}
|
||||
|
||||
val scanResponse = unlockMethod.scanResponse ?: run {
|
||||
|
|
@ -276,24 +287,27 @@ internal class DefaultUserWalletsListRepository(
|
|||
|
||||
val encryptionKey = UserWalletEncryptionKey(
|
||||
walletId = userWallet.walletId,
|
||||
encryptionKey = scanResponse.encryptionKey ?: raise(UnlockWalletError.UnableToUnlock),
|
||||
encryptionKey = scanResponse.encryptionKey ?: raise(UnlockWalletError.UnableToUnlock.Empty),
|
||||
)
|
||||
|
||||
sensitiveInformationRepository.getAll(listOf(encryptionKey))
|
||||
.doOnSuccess { sensitiveInfo -> updateWallets { it?.updateWith(sensitiveInfo) } }
|
||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock) }
|
||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun unlockAllWallets(): Either<UnlockWalletError, Unit> = either {
|
||||
val userWallets = userWalletsSync()
|
||||
val userWalletIds = userWallets.map { it.walletId }.toSet()
|
||||
val biometricKeys = runCatching {
|
||||
val userWallets = userWalletsSync().filter { it.isLocked }
|
||||
|
||||
if (userWallets.isEmpty()) {
|
||||
return@either
|
||||
}
|
||||
|
||||
val biometricKeys = runSuspendCatching {
|
||||
userWalletEncryptionKeysRepository.getAllBiometric()
|
||||
}.getOrElse {
|
||||
// TODO handle error properly [REDACTED_TASK_KEY]
|
||||
raise(UnlockWalletError.UserCancelled)
|
||||
}.getOrElse { exception ->
|
||||
catchBiometricException(exception)
|
||||
}
|
||||
|
||||
val unsecuredKeys = userWalletEncryptionKeysRepository.getAllUnsecured()
|
||||
|
|
@ -309,16 +323,17 @@ internal class DefaultUserWalletsListRepository(
|
|||
removePasswordAttempts(it)
|
||||
}
|
||||
|
||||
// if we cant unlock all wallets
|
||||
if (userWalletIds.all { it in unlockedWalletsIds }.not()) {
|
||||
raise(UnlockWalletError.UnableToUnlock)
|
||||
// if we cant unlock any of the locked wallets, return error
|
||||
// (isLocked remains `true` here because we haven't updated the wallets yet)
|
||||
if (unlockedWallets.any { it.isLocked }.not()) {
|
||||
raise(UnlockWalletError.UnableToUnlock.Empty)
|
||||
}
|
||||
|
||||
sensitiveInformationRepository.getAll(allKeys)
|
||||
.doOnSuccess { sensitiveInfo ->
|
||||
updateWallets { userWallets.updateWith(sensitiveInfo) }
|
||||
updateWallets { wallets -> wallets?.updateWith(sensitiveInfo) }
|
||||
}
|
||||
.doOnFailure { raise(UnlockWalletError.UnableToUnlock) }
|
||||
.doOnFailure { error -> raise(UnlockWalletError.UnableToUnlock.RawException(error)) }
|
||||
}
|
||||
|
||||
override suspend fun lockAllWallets(): Either<LockWalletsError, Unit> = either {
|
||||
|
|
@ -345,6 +360,40 @@ internal class DefaultUserWalletsListRepository(
|
|||
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 checkForUpgradeAndDeleteHotWalletIfNeeded(
|
||||
newUserWallet: UserWallet,
|
||||
oldUserWallet: UserWallet,
|
||||
) {
|
||||
if (newUserWallet.walletId == oldUserWallet.walletId &&
|
||||
oldUserWallet is UserWallet.Hot && newUserWallet is UserWallet.Cold
|
||||
) {
|
||||
removeHotWalletsFromSDK(walletIds = listOf(oldUserWallet.walletId))
|
||||
// When upgrading from Hot to Cold, if biometric lock is available, set it
|
||||
if (hasBiometry()) {
|
||||
setLock(newUserWallet.walletId, LockMethod.Biometric, changeUnsecured = true)
|
||||
}
|
||||
// Remove any encryption keys related to the old hot wallet
|
||||
userWalletEncryptionKeysRepository.removeEncryptedWithPasswordKey(oldUserWallet.walletId)
|
||||
userWalletEncryptionKeysRepository.removeUnsecuredKey(oldUserWallet.walletId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun removeHotWalletsFromSDK(walletIds: List<UserWalletId>) {
|
||||
val hotWalletsToDelete = userWalletsSync()
|
||||
.filterIsInstance<UserWallet.Hot>()
|
||||
.filter { walletIds.contains(it.walletId) }
|
||||
|
||||
hotWalletsToDelete.forEach {
|
||||
tangemHotSdk.delete(it.hotWalletId)
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun requestPasswordRecursive(
|
||||
hotWalletId: HotWalletId,
|
||||
block: suspend (CharArray) -> UserWalletEncryptionKey?,
|
||||
|
|
@ -410,6 +459,27 @@ internal class DefaultUserWalletsListRepository(
|
|||
}
|
||||
}
|
||||
|
||||
private fun Raise<UnlockWalletError>.catchBiometricException(ex: Throwable): Nothing {
|
||||
val reason = when (ex) {
|
||||
is TangemSdkError.AuthenticationLockout ->
|
||||
UnlockWalletError.UnableToUnlock.Reason.BiometricsAuthenticationLockout(isPermanent = false)
|
||||
is TangemSdkError.AuthenticationPermanentLockout ->
|
||||
UnlockWalletError.UnableToUnlock.Reason.BiometricsAuthenticationLockout(isPermanent = true)
|
||||
is TangemSdkError.KeystoreInvalidated ->
|
||||
UnlockWalletError.UnableToUnlock.Reason.AllKeysInvalidated
|
||||
is TangemSdkError.AuthenticationUnavailable ->
|
||||
UnlockWalletError.UnableToUnlock.Reason.BiometricsAuthenticationDisabled
|
||||
|
||||
is TangemSdkError.AuthenticationCanceled,
|
||||
is TangemSdkError.AuthenticationAlreadyInProgress,
|
||||
-> raise(UnlockWalletError.UserCancelled)
|
||||
|
||||
else -> raise(UnlockWalletError.UnableToUnlock.RawException(ex))
|
||||
}
|
||||
|
||||
raise(UnlockWalletError.UnableToUnlock.WithReason(reason))
|
||||
}
|
||||
|
||||
/**
|
||||
* Find the nearest available wallet that can be selected
|
||||
*
|
||||
|
|
|
|||
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