diff --git a/.github/workflows/e2e-main.yml b/.github/workflows/e2e-main.yml index 4ab61e54324..be9edc6721b 100644 --- a/.github/workflows/e2e-main.yml +++ b/.github/workflows/e2e-main.yml @@ -22,24 +22,21 @@ concurrency: cancel-in-progress: true jobs: - # TODO: enable once we have at least one android-api-level below - # android: - # name: Android - # strategy: - # max-parallel: 2 - # fail-fast: false - # matrix: - # # TODO if/when more CI machines: enable Android sdk 21, 23, 29 and 31 - # # 21 is failing 9 specs - # # 23 is failing ? spec(s) - # # 31 is failing ? spec(s) - # # 24 is currently failing all the time, disabling it for now - # # 30 is not included as it runs on the merge queue - # android-api-level: [] - # uses: ./.github/workflows/e2e-android.yml - # with: - # android-api-level: ${{ matrix.android-api-level }} - # secrets: inherit + android: + name: Android + strategy: + max-parallel: 2 + fail-fast: false + matrix: + # min supported API level is 24 + # 24 is failing due to Let's Encrypt root certificate expiration + # 25 is failing (RET-1274) + # 26 is failing (RET-1275) + android-api-level: ['27'] + uses: ./.github/workflows/e2e-android.yml + with: + android-api-level: ${{ matrix.android-api-level }} + secrets: inherit ios: name: iOS strategy: diff --git a/__mocks__/@react-native-firebase/dynamic-links.ts b/__mocks__/@react-native-firebase/dynamic-links.ts index 2e6ee39ecc0..bdf511dfe44 100644 --- a/__mocks__/@react-native-firebase/dynamic-links.ts +++ b/__mocks__/@react-native-firebase/dynamic-links.ts @@ -1,11 +1,13 @@ const getInitialLink = jest.fn() const buildShortLink = jest.fn() const buildLink = jest.fn() +const onLink = jest.fn() export default function links() { return { getInitialLink, buildShortLink, buildLink, + onLink, } } diff --git a/__mocks__/clevertap-react-native.ts b/__mocks__/clevertap-react-native.ts index 4f8dbca1a2f..e26530fd0c5 100644 --- a/__mocks__/clevertap-react-native.ts +++ b/__mocks__/clevertap-react-native.ts @@ -2,4 +2,7 @@ export default { setPushToken: jest.fn(), createNotificationChannel: jest.fn(), registerForPush: jest.fn(), + getInitialUrl: jest.fn(), + addListener: jest.fn(), + removeListener: jest.fn(), } diff --git a/android/app/build.gradle b/android/app/build.gradle index ea1ae915662..9e28af28d2c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -150,7 +150,7 @@ android { minSdkVersion rootProject.ext.minSdkVersion targetSdkVersion rootProject.ext.targetSdkVersion versionCode appVersionCode - versionName "1.100.0" + versionName "1.101.0" multiDexEnabled true testBuildType System.getProperty('testBuildType', 'debug') testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" diff --git a/android/gradle.properties b/android/gradle.properties index 47b5778c8e5..4e41130e3f0 100644 --- a/android/gradle.properties +++ b/android/gradle.properties @@ -25,7 +25,7 @@ RELEASE_KEY_ALIAS=mobilestack-key-alias # Setting this manually based on version number until we have this deploying via Cloud Build # Example: v1.5.1 deployment number 1 = 1005001001 (1 005 001 001) -VERSION_CODE=1021071684 +VERSION_CODE=1021071685 # AndroidX package structure to make it clearer which packages are bundled with the # Android operating system, and which are packaged with your app's APK # https://developer.android.com/topic/libraries/support-library/androidx-rn diff --git a/e2e/src/Discover.spec.js b/e2e/src/Discover.spec.js index 59d2f94c7ac..7c7565bf931 100644 --- a/e2e/src/Discover.spec.js +++ b/e2e/src/Discover.spec.js @@ -1,6 +1,6 @@ import DappListDisplay from './usecases/DappListDisplay' import { launchApp } from './utils/retries' -import { quickOnboarding, scrollIntoView, waitForElementByIdAndTap } from './utils/utils' +import { quickOnboarding, scrollIntoView, waitForElementById } from './utils/utils' describe('Discover tab', () => { beforeAll(async () => { @@ -8,7 +8,7 @@ describe('Discover tab', () => { // Relaunch app to ensure dapp list loads // Needed for e2e tests otherwise dapp list is not loaded on first pass await launchApp() - await waitForElementByIdAndTap('Tab/Discover') + await waitForElementById('Tab/Discover', { tap: true }) await scrollIntoView('View All', 'DiscoverScrollView') await element(by.text('View All')).tap() diff --git a/e2e/src/QRScanner.spec.js b/e2e/src/QRScanner.spec.js index bae0a69b825..a63e27858a0 100644 --- a/e2e/src/QRScanner.spec.js +++ b/e2e/src/QRScanner.spec.js @@ -1,5 +1,5 @@ import { reloadReactNative } from './utils/retries' -import { quickOnboarding, waitForElementId } from './utils/utils' +import { quickOnboarding, waitForElementById } from './utils/utils' describe('Given QR Scanner', () => { beforeAll(async () => { @@ -9,30 +9,30 @@ describe('Given QR Scanner', () => { describe('When opening QR scanner', () => { it('Then should display QR code', async () => { await reloadReactNative() - await waitForElementId('HomeAction-Receive') + await waitForElementById('HomeAction-Receive') await element(by.id('HomeAction-Receive')).tap() - await waitForElementId('QRCode') + await waitForElementById('QRCode') await expect(element(by.id('QRCode'))).toBeVisible() }) it('Then should be able to toggle camera', async () => { - await waitForElementId('Scan') + await waitForElementById('Scan') await element(by.id('Scan')).tap() - await waitForElementId('CameraScanInfo') + await waitForElementById('CameraScanInfo') await expect(element(by.id('CameraScanInfo'))).toBeVisible() }) it('Then should be able to toggle to QR code', async () => { - await waitForElementId('My Code') + await waitForElementById('My Code') await element(by.id('My Code')).tap() - await waitForElementId('QRCode') + await waitForElementById('QRCode') await expect(element(by.id('QRCode'))).toBeVisible() }) it('Then should be able to close QR code scanner', async () => { - await waitForElementId('Times') + await waitForElementById('Times') await element(by.id('Times')).tap() - await waitForElementId('HomeAction-Send') + await waitForElementById('HomeAction-Send') await expect(element(by.id('HomeAction-Send'))).toBeVisible() }) }) @@ -40,34 +40,34 @@ describe('Given QR Scanner', () => { describe("When 'scanning' QR", () => { beforeEach(async () => { await reloadReactNative() - await waitForElementId('HomeAction-Receive') + await waitForElementById('HomeAction-Receive') await element(by.id('HomeAction-Receive')).tap() - await waitForElementId('Scan') + await waitForElementById('Scan') await element(by.id('Scan')).tap() - await waitForElementId('CameraScanInfo') + await waitForElementById('CameraScanInfo') await element(by.id('CameraScanInfo')).tap() }) it('Then should be able to handle Celo pay QR', async () => { - await waitForElementId('ManualInput') + await waitForElementById('ManualInput') await element(by.id('ManualInput')).replaceText( 'celo://wallet/pay?address=0xe5F5363e31351C38ac82DBAdeaD91Fd5a7B08846' ) - await waitForElementId('ManualSubmit') + await waitForElementById('ManualSubmit') await element(by.id('ManualSubmit')).tap() - await waitForElementId('SendEnterAmount/AmountOptions') + await waitForElementById('SendEnterAmount/AmountOptions') await element(by.text('Done')).tap() // dismiss the keyboard to reveal the proceed button await expect(element(by.id('SendEnterAmount/ReviewButton'))).toBeVisible() }) it('Then should handle address only QR', async () => { - await waitForElementId('ManualInput') + await waitForElementById('ManualInput') await element(by.id('ManualInput')).replaceText('0xe5F5363e31351C38ac82DBAdeaD91Fd5a7B08846') - await waitForElementId('ManualSubmit') + await waitForElementById('ManualSubmit') await element(by.id('ManualSubmit')).tap() - await waitForElementId('SendEnterAmount/AmountOptions') + await waitForElementById('SendEnterAmount/AmountOptions') await element(by.text('Done')).tap() // dismiss the keyboard to reveal the proceed button await expect(element(by.id('SendEnterAmount/ReviewButton'))).toBeVisible() }) diff --git a/e2e/src/usecases/Assets.js b/e2e/src/usecases/Assets.js index 14ed6c5d22e..9c8c952b978 100644 --- a/e2e/src/usecases/Assets.js +++ b/e2e/src/usecases/Assets.js @@ -6,19 +6,18 @@ import { getDisplayAddress, quickOnboarding, scrollIntoViewByTestId, - waitForElementByIdAndTap, - waitForElementId, + waitForElementById, } from '../utils/utils' async function validateSendFlow(tokenSymbol) { const recipientAddressDisplay = getDisplayAddress(DEFAULT_RECIPIENT_ADDRESS) // navigate to send amount screen to ensure the expected token symbol is pre-selected - await waitForElementByIdAndTap('SendSelectRecipientSearchInput') + await waitForElementById('SendSelectRecipientSearchInput', { tap: true }) await element(by.id('SendSelectRecipientSearchInput')).replaceText(DEFAULT_RECIPIENT_ADDRESS) await element(by.id('SendSelectRecipientSearchInput')).tapReturnKey() await expect(element(by.text(recipientAddressDisplay)).atIndex(0)).toBeVisible() await element(by.text(recipientAddressDisplay)).atIndex(0).tap() - await waitForElementByIdAndTap('SendOrInviteButton') + await waitForElementById('SendOrInviteButton', { tap: true }) await expect( element(by.text(`${tokenSymbol} on Celo`).withAncestor(by.id('SendEnterAmount/TokenSelect'))) ).toBeVisible() @@ -89,29 +88,29 @@ export default Assets = () => { }) it('navigates to wallet tab from home', async () => { - await waitForElementByIdAndTap('Tab/Wallet') - await waitForElementId('Assets/TabBar') + await waitForElementById('Tab/Wallet', { tap: true }) + await waitForElementById('Assets/TabBar') }) it('switching tabs displays corresponding assets', async () => { await expect(element(by.id('TokenBalanceItem')).atIndex(0)).toBeVisible() await element(by.id('Assets/TabBarItem')).atIndex(1).tap() - await waitForElementId('Assets/NoNfts') + await waitForElementById('Assets/NoNfts') await element(by.id('Assets/TabBarItem')).atIndex(0).tap() await expect(element(by.id('TokenBalanceItem')).atIndex(0)).toBeVisible() }) describe.each(tokens)('For $symbol', ({ symbol, tokenId, learnMore, actions, moreActions }) => { it('navigates to asset details on tapping asset', async () => { - await waitForElementByIdAndTap(`TokenBalanceItemTouchable/${tokenId}`) - await waitForElementId('TokenDetails/AssetValue') + await waitForElementById(`TokenBalanceItemTouchable/${tokenId}`, { tap: true }) + await waitForElementById('TokenDetails/AssetValue') }) if (actions.includes('Send')) { it('send action navigates to send flow', async () => { await element(by.id('TokenDetails/Action/Send')).tap() await validateSendFlow(symbol) - await waitForElementId('TokenDetails/AssetValue') + await waitForElementById('TokenDetails/AssetValue') }) } @@ -119,54 +118,54 @@ export default Assets = () => { it('add action navigates to add cico flow', async () => { await element(by.id('TokenDetails/Action/Add')).tap() await validateAddFlow(symbol) - await waitForElementId('TokenDetails/AssetValue') + await waitForElementById('TokenDetails/AssetValue') }) } if (moreActions.includes('Send')) { it('send action under more actions navigates to send flow', async () => { await element(by.id('TokenDetails/Action/More')).tap() - await waitForElementByIdAndTap('TokenDetailsMoreActions/Send') + await waitForElementById('TokenDetailsMoreActions/Send', { tap: true }) await validateSendFlow(symbol) - await waitForElementId('TokenDetails/AssetValue') + await waitForElementById('TokenDetails/AssetValue') }) } if (moreActions.includes('Add')) { it('add action under more actions navigates to add cico flow', async () => { await element(by.id('TokenDetails/Action/More')).tap() - await waitForElementByIdAndTap('TokenDetailsMoreActions/Add') + await waitForElementById('TokenDetailsMoreActions/Add', { tap: true }) await validateAddFlow(symbol) - await waitForElementId('TokenDetails/AssetValue') + await waitForElementById('TokenDetails/AssetValue') }) } if (moreActions.includes('Withdraw')) { it('withdraw action under more actions navigates to withdraw spend screen', async () => { await element(by.id('TokenDetails/Action/More')).tap() - await waitForElementByIdAndTap('TokenDetailsMoreActions/Withdraw') - await waitForElementId('FiatExchangeTokenBalance') + await waitForElementById('TokenDetailsMoreActions/Withdraw', { tap: true }) + await waitForElementById('FiatExchangeTokenBalance') await element(by.id('BackChevron')).tap() - await waitForElementId('TokenDetails/AssetValue') + await waitForElementById('TokenDetails/AssetValue') }) } if (learnMore) { it('learn more navigates to coingecko page', async () => { await scrollIntoViewByTestId('TokenDetails/LearnMore', 'TokenDetailsScrollView') - await waitForElementByIdAndTap('TokenDetails/LearnMore') - await waitForElementId('RNWebView') + await waitForElementById('TokenDetails/LearnMore', { tap: true }) + await waitForElementById('RNWebView') await waitFor(element(by.text('www.coingecko.com'))) .toBeVisible() .withTimeout(10 * 1000) await element(by.id('WebViewScreen/CloseButton')).tap() - await waitForElementId('TokenBalanceItem') + await waitForElementById('TokenBalanceItem') }) } it('navigates back to Assets page', async () => { await element(by.id('BackChevron')).tap() - await waitForElementId('Assets/TabBar') + await waitForElementById('Assets/TabBar') }) }) }) diff --git a/e2e/src/usecases/CeloEducation.js b/e2e/src/usecases/CeloEducation.js index 612b85c74a2..3e436d10c8e 100644 --- a/e2e/src/usecases/CeloEducation.js +++ b/e2e/src/usecases/CeloEducation.js @@ -1,5 +1,5 @@ import { celoEducation } from '../utils/celoEducation' -import { waitForElementByIdAndTap, waitForElementId } from '../utils/utils' +import { waitForElementById } from '../utils/utils' const swipeThrough = async (direction = 'left', swipes = 3) => { for (let i = 0; i < swipes; i++) { @@ -10,11 +10,11 @@ const swipeThrough = async (direction = 'left', swipes = 3) => { const tapThrough = async (direction = 'forward', steps = 3) => { if (direction === 'forward') { for (let i = 0; i < steps; i++) { - await waitForElementByIdAndTap('Education/progressButton') + await waitForElementById('Education/progressButton', { tap: true }) } } else { for (let i = 0; i < steps; i++) { - await waitForElementByIdAndTap('Education/BackIcon') + await waitForElementById('Education/BackIcon', { tap: true }) } } } @@ -27,8 +27,8 @@ const progressButtonCheck = async (text = 'Next', timeout = 10 * 1000) => { export default CeloEducation = () => { beforeAll(async () => { - await waitForElementByIdAndTap('WalletHome/NotificationBell') - await waitForElementId('NotificationView/celo_asset_education') + await waitForElementById('WalletHome/NotificationBell', { tap: true }) + await waitForElementById('NotificationView/celo_asset_education') await element( by.text('Learn More').withAncestor(by.id('NotificationView/celo_asset_education')) ).tap() @@ -36,10 +36,10 @@ export default CeloEducation = () => { it('should be able to navigate with swipes', async () => { await swipeThrough() - await waitForElementId('Education/BackIcon') + await waitForElementById('Education/BackIcon') await progressButtonCheck('Done') await swipeThrough('right') - await waitForElementId('Education/CloseIcon') + await waitForElementById('Education/CloseIcon') await progressButtonCheck('Next') }) @@ -47,13 +47,13 @@ export default CeloEducation = () => { await tapThrough() await progressButtonCheck('Done') await tapThrough('back') - await waitForElementId('Education/CloseIcon') + await waitForElementById('Education/CloseIcon') await progressButtonCheck('Next') }) it('should be able to close CELO education carousel', async () => { - await waitForElementByIdAndTap('Education/CloseIcon') - await waitForElementId('NotificationView/celo_asset_education') + await waitForElementById('Education/CloseIcon', { tap: true }) + await waitForElementById('NotificationView/celo_asset_education') }) it('should be able to complete CELO education carousel', async () => { @@ -61,6 +61,6 @@ export default CeloEducation = () => { by.text('Learn More').withAncestor(by.id('NotificationView/celo_asset_education')) ).tap() await celoEducation() - await waitForElementId('Tab/Home') + await waitForElementById('Tab/Home') }) } diff --git a/e2e/src/usecases/ChooseYourAdventure.js b/e2e/src/usecases/ChooseYourAdventure.js index db0c4b9cb59..3dde31a5a3f 100644 --- a/e2e/src/usecases/ChooseYourAdventure.js +++ b/e2e/src/usecases/ChooseYourAdventure.js @@ -1,5 +1,5 @@ import { launchApp } from '../utils/retries' -import { quickOnboarding, waitForElementByText, waitForElementId } from '../utils/utils' +import { quickOnboarding, waitForElementById, waitForElementByText } from '../utils/utils' export default ChooseYourAdventure = () => { beforeEach(async () => { @@ -20,36 +20,36 @@ export default ChooseYourAdventure = () => { // Back should go to the home screen await element(by.id('BackChevron')).tap() - await waitForElementId('HomeAction-Send') + await waitForElementById('HomeAction-Send') }) it('build your profile navigates to profile page', async () => { await waitForElementByText({ text: 'Build your profile', tap: true }) // Check that we are on the profile page - await waitForElementId('ProfileEditName') + await waitForElementById('ProfileEditName') // Back should go to the home screen await element(by.id('BackButton')).tap() - await waitForElementId('HomeAction-Send') + await waitForElementById('HomeAction-Send') }) it('add funds to your wallet navigates to home and opens the token bottom sheet', async () => { await waitForElementByText({ text: 'Add funds to your wallet', tap: true }) // Check that we are on the bottom sheet - await waitForElementId('TokenBottomSheet') + await waitForElementById('TokenBottomSheet') // dismissing the bottom sheet should show home screen await element(by.id('TokenBottomSheet')).swipe('down') - await waitForElementId('HomeAction-Send') + await waitForElementById('HomeAction-Send') }) it('explore earning opportunities navigates to stablecoins info page', async () => { await waitForElementByText({ text: 'Explore earning opportunities', tap: true }) // Check that we are on the Earn On Your Stablecoins page - await waitForElementId('EarnInfoScreen/Title') + await waitForElementById('EarnInfoScreen/Title') await waitForElementByText({ text: 'Earn on your\ncrypto' }) }) } diff --git a/e2e/src/usecases/HandleDeepLinkSend.js b/e2e/src/usecases/HandleDeepLinkSend.js index 518d7732cd0..a84eb684d83 100644 --- a/e2e/src/usecases/HandleDeepLinkSend.js +++ b/e2e/src/usecases/HandleDeepLinkSend.js @@ -1,7 +1,7 @@ import jestExpect from 'expect' import { E2E_TEST_FAUCET } from '../../scripts/consts' import { launchApp, reloadReactNative } from '../utils/retries' -import { enterPinUiIfNecessary, waitForElementByIdAndTap, waitForElementId } from '../utils/utils' +import { enterPinUiIfNecessary, waitForElementById } from '../utils/utils' const deepLinks = { withoutAddress: @@ -63,11 +63,14 @@ export default HandleDeepLinkSend = () => { await launchDeepLink({ url: `celo://wallet/pay?address=${E2E_TEST_FAUCET}¤cyCode=USD&token=cUSD&displayName=TestFaucet`, }) - await waitForElementId('SendEnterAmount/TokenSelect', 10_000) + await waitForElementById('SendEnterAmount/TokenSelect') await expect(element(by.text('cUSD on Celo')).atIndex(0)).toBeVisible() await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01') await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey() - await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000) + await waitForElementById('SendEnterAmount/ReviewButton', { + timeout: 30_000, + tap: true, + }) // Send Transaction await element(by.id('ConfirmButton')).tap() diff --git a/e2e/src/usecases/HomeFeed.js b/e2e/src/usecases/HomeFeed.js index 33a7592ac72..4398e795fc6 100644 --- a/e2e/src/usecases/HomeFeed.js +++ b/e2e/src/usecases/HomeFeed.js @@ -1,11 +1,11 @@ import jestExpect from 'expect' -import { waitForElementId } from '../utils/utils' +import { waitForElementById } from '../utils/utils' import { sleep } from '../../../src/utils/sleep' export default HomeFeed = () => { it('should show correct information on tap of feed item', async () => { // Load Wallet Home - await waitForElementId('WalletHome') + await waitForElementById('WalletHome') const items = await element(by.id('TransferFeedItem')).getAttributes() // Tap top TransferFeedItem @@ -27,7 +27,7 @@ export default HomeFeed = () => { } catch {} // Load Wallet Home - await waitForElementId('WalletHome') + await waitForElementById('WalletHome') const startingItems = await element(by.id('TransferFeedItem')).getAttributes() // Scroll to bottom - Android will scroll forever so we set a static value diff --git a/e2e/src/usecases/NewAccountOnboarding.js b/e2e/src/usecases/NewAccountOnboarding.js index 354fe5d61e7..e9fdb0cc754 100644 --- a/e2e/src/usecases/NewAccountOnboarding.js +++ b/e2e/src/usecases/NewAccountOnboarding.js @@ -5,8 +5,7 @@ import { enterPinUi, navigateToSecurity, quickOnboarding, - waitForElementByIdAndTap, - waitForElementId, + waitForElementById, } from '../utils/utils' import jestExpect from 'expect' @@ -15,8 +14,8 @@ const startBackupFromNotifications = async () => { await element(by.id('WalletHome/NotificationBell')).tap() await element(by.text('Back up now')).tap() await enterPinUi() - await waitForElementByIdAndTap('WalletSecurityPrimer/GetStarted') - await waitForElementByIdAndTap('keylessBackupIntro/RecoveryPhrase') + await waitForElementById('WalletSecurityPrimer/GetStarted', { tap: true }) + await waitForElementById('keylessBackupIntro/RecoveryPhrase', { tap: true }) await element(by.id('SetUpAccountKey')).tap() // Go through education @@ -65,13 +64,13 @@ export default NewAccountOnboarding = () => { await element(by.id('PhoneVerificationSkipHeader')).tap() // Choose your own adventure (CYA screen) - await waitForElementByIdAndTap('ChooseYourAdventure/Later') + await waitForElementById('ChooseYourAdventure/Later', { tap: true }) // Arrived to Home screen await arriveAtHomeScreen() // Able to open settings - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') + await waitForElementById('WalletHome/SettingsGearButton', { tap: true }) await element(by.id('Times')).tap() }) @@ -117,15 +116,15 @@ export default NewAccountOnboarding = () => { }) it('Account Address shown in profile / menu', async () => { - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') - await waitForElementByIdAndTap('SettingsMenu/Address') + await waitForElementById('WalletHome/SettingsGearButton', { tap: true }) + await waitForElementById('SettingsMenu/Address', { tap: true }) const accountAddressElement = await element(by.id('address')).getAttributes() const accountAddressText = accountAddressElement.text.replace(/\s/g, '') testAccountAddress = accountAddressText jestExpect(testAccountAddress).toMatch(/0x[0-9a-fA-F]{40}/) await element(by.id('BackChevron')).tap() - await waitForElementByIdAndTap('Times') + await waitForElementById('Times', { tap: true }) }) // After quiz completion recovery phrase should only be shown in settings and @@ -135,10 +134,10 @@ export default NewAccountOnboarding = () => { await expect(element(by.text('Back up now'))).not.toExist() await element(by.id('BackChevron')).tap() await navigateToSecurity() - await waitForElementId('RecoveryPhrase') + await waitForElementById('RecoveryPhrase') await element(by.id('RecoveryPhrase')).tap() await enterPinUi() - await waitForElementId('AccountKeyWordsContainer') + await waitForElementById('AccountKeyWordsContainer') }) // Based off the flag set in src/firebase/remoteConfigValuesDefaults.e2e.ts @@ -158,8 +157,8 @@ export default NewAccountOnboarding = () => { }, }) await quickOnboarding({ mnemonic: testRecoveryPhrase, cloudBackupEnabled: true }) - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') - await waitForElementByIdAndTap('SettingsMenu/Address') + await waitForElementById('WalletHome/SettingsGearButton', { tap: true }) + await waitForElementById('SettingsMenu/Address', { tap: true }) await expect(element(by.text(testAccountAddress))).toBeVisible() }) diff --git a/e2e/src/usecases/OffRamps.js b/e2e/src/usecases/OffRamps.js index b1d998a07c7..9061cc2908a 100644 --- a/e2e/src/usecases/OffRamps.js +++ b/e2e/src/usecases/OffRamps.js @@ -1,5 +1,5 @@ import { launchApp, reloadReactNative } from '../utils/retries' -import { waitForElementId } from '../utils/utils' +import { waitForElementById } from '../utils/utils' export default offRamps = () => { beforeAll(async () => { @@ -7,9 +7,9 @@ export default offRamps = () => { }) beforeEach(async () => { await reloadReactNative() - await waitForElementId('HomeActionsCarousel') + await waitForElementById('HomeActionsCarousel') await element(by.id('HomeActionsCarousel')).scrollTo('right') - await waitForElementId('HomeAction-Withdraw') + await waitForElementById('HomeAction-Withdraw') await element(by.id('HomeAction-Withdraw')).tap() }) @@ -20,7 +20,7 @@ export default offRamps = () => { }) it('Then should display total balance', async () => { - await waitForElementId('ViewBalances') + await waitForElementById('ViewBalances') await element(by.id('ViewBalances')).tap() await expect(element(by.id('AssetsTokenBalance'))).toBeVisible() }) @@ -28,30 +28,30 @@ export default offRamps = () => { describe('When Spend selected', () => { beforeEach(async () => { - await waitForElementId('spend') + await waitForElementById('spend') await element(by.id('spend')).tap() }) it('Then should be able to spend cUSD', async () => { - await waitForElementId(`cUSDSymbol`) + await waitForElementById('cUSDSymbol') await element(by.id(`cUSDSymbol`)).tap() - await waitForElementId('RNWebView') + await waitForElementById('RNWebView') await expect(element(by.text('Bidali'))).toBeVisible() }) it('Then should be able to spend cEUR', async () => { - await waitForElementId(`cEURSymbol`) - await element(by.id(`cEURSymbol`)).tap() + await waitForElementById('cEURSymbol') + await element(by.id('cEURSymbol')).tap() - await waitForElementId('RNWebView') + await waitForElementById('RNWebView') await expect(element(by.text('Bidali'))).toBeVisible() }) }) describe('When Withdraw Selected', () => { beforeEach(async () => { - await waitForElementId('cashOut') + await waitForElementById('cashOut') await element(by.id('cashOut')).tap() }) @@ -65,30 +65,30 @@ export default offRamps = () => { `( 'Then should display at least $exchanges.minExpected $token exchange(s)', async ({ token, exchanges }) => { - await waitForElementId(`${token}Symbol`) + await waitForElementById(`${token}Symbol`) await element(by.id(`${token}Symbol`)).tap() - await waitForElementId('FiatExchangeInput') + await waitForElementById('FiatExchangeInput') await element(by.id('FiatExchangeInput')).replaceText('2') await element(by.id('FiatExchangeNextButton')).tap() await expect(element(by.text('Select Withdraw Method'))).toBeVisible() - await waitForElementId('Exchanges') + await waitForElementById('Exchanges') await element(by.id('Exchanges')).tap() - await waitForElementId('SendBar') + await waitForElementById('SendBar') // Exchanges start at index 0 - await waitForElementId(`provider-${exchanges.minExpected - 1}`) + await waitForElementById(`provider-${exchanges.minExpected - 1}`) } ) it('Then Send To Address', async () => { const randomAmount = `${(Math.random() * 10 ** -1).toFixed(3)}` - await waitForElementId(`CELOSymbol`) + await waitForElementById('CELOSymbol') await element(by.id(`CELOSymbol`)).tap() - await waitForElementId('FiatExchangeInput') + await waitForElementById('FiatExchangeInput') await element(by.id('FiatExchangeInput')).replaceText(`${randomAmount}`) await element(by.id('FiatExchangeNextButton')).tap() - await waitForElementId('Exchanges') + await waitForElementById('Exchanges') await element(by.id('Exchanges')).tap() await element(by.id('SendBar')).tap() await waitFor(element(by.id('SendSelectRecipientSearchInput'))) diff --git a/e2e/src/usecases/OnRamps.js b/e2e/src/usecases/OnRamps.js index bdb9a1a9203..22ceffa41c6 100644 --- a/e2e/src/usecases/OnRamps.js +++ b/e2e/src/usecases/OnRamps.js @@ -1,5 +1,5 @@ import { launchApp, reloadReactNative } from '../utils/retries' -import { isElementVisible, waitForElementId } from '../utils/utils' +import { isElementVisible, waitForElementById } from '../utils/utils' export default onRamps = () => { beforeAll(async () => { @@ -7,7 +7,7 @@ export default onRamps = () => { }) beforeEach(async () => { await reloadReactNative() - await waitForElementId('HomeAction-Add') + await waitForElementById('HomeAction-Add') await element(by.id('HomeAction-Add')).tap() }) @@ -21,10 +21,10 @@ export default onRamps = () => { ${'CELO'} | ${'20'} ${'CELO'} | ${'2'} `('Then should display $token provider(s) for $$amount', async ({ token, amount }) => { - await waitForElementId(`${token}Symbol`) + await waitForElementById(`${token}Symbol`) await element(by.id(`${token}Symbol`)).tap() - await waitForElementId('FiatExchangeInput') + await waitForElementById('FiatExchangeInput') await element(by.id('FiatExchangeInput')).replaceText(`${amount}`) await element(by.id('FiatExchangeNextButton')).tap() await expect(element(by.text('Select Payment Method'))).toBeVisible() diff --git a/e2e/src/usecases/PINChange.js b/e2e/src/usecases/PINChange.js index aa73bdb6b23..c6f049a5280 100644 --- a/e2e/src/usecases/PINChange.js +++ b/e2e/src/usecases/PINChange.js @@ -1,13 +1,13 @@ import { sleep } from '../../../src/utils/sleep' import { ALTERNATIVE_PIN, DEFAULT_PIN } from '../utils/consts' import { reloadReactNative } from '../utils/retries' -import { enterPinUi, navigateToSecurity, waitForElementId } from '../utils/utils' +import { enterPinUi, navigateToSecurity, waitForElementById } from '../utils/utils' export default ChangePIN = () => { it('Then should be retain changed PIN', async () => { await navigateToSecurity() - await waitForElementId('ChangePIN') + await waitForElementById('ChangePIN') await element(by.id('ChangePIN')).tap() // Existing PIN is needed first await sleep(500) diff --git a/e2e/src/usecases/ResetAccount.js b/e2e/src/usecases/ResetAccount.js index b4256e3ad01..525256ed6b4 100644 --- a/e2e/src/usecases/ResetAccount.js +++ b/e2e/src/usecases/ResetAccount.js @@ -1,6 +1,6 @@ import { E2E_WALLET_MNEMONIC } from 'react-native-dotenv' import { reloadReactNative } from '../utils/retries' -import { enterPinUiIfNecessary, navigateToSecurity, waitForElementId } from '../utils/utils' +import { enterPinUiIfNecessary, navigateToSecurity, waitForElementById } from '../utils/utils' export default ResetAccount = () => { beforeEach(async () => { @@ -32,7 +32,7 @@ export default ResetAccount = () => { await element(by.id('backupKeySavedSwitch')).longPress() await element(by.id('backupKeyContinue')).tap() const mnemonic = E2E_WALLET_MNEMONIC.split(' ') - await waitForElementId(`backupQuiz/${mnemonic[0]}`) + await waitForElementById(`backupQuiz/${mnemonic[0]}`) for (const word of mnemonic) { await element(by.id(`backupQuiz/${word}`)).tap() } @@ -42,7 +42,7 @@ export default ResetAccount = () => { // TODO: Figure out a way to confirm and test that the app goes to the onboarding // screen on next open. // await element(by.id('ConfirmAccountRemovalModal/PrimaryAction')).tap() - await waitForElementId('ConfirmAccountRemovalModal/PrimaryAction') + await waitForElementById('ConfirmAccountRemovalModal/PrimaryAction') await expect(element(by.id('ConfirmAccountRemovalModal/PrimaryAction'))).toBeVisible() }) } diff --git a/e2e/src/usecases/RestoreAccountOnboarding.js b/e2e/src/usecases/RestoreAccountOnboarding.js index 9efc9ac3560..21254fa8fbe 100644 --- a/e2e/src/usecases/RestoreAccountOnboarding.js +++ b/e2e/src/usecases/RestoreAccountOnboarding.js @@ -1,7 +1,7 @@ import { E2E_WALLET_12_WORDS_MNEMONIC, E2E_WALLET_MNEMONIC } from 'react-native-dotenv' import { WALLET_12_WORDS_ADDRESS, WALLET_ADDRESS } from '../utils/consts' import { launchApp } from '../utils/retries' -import { enterPinUi, scrollIntoView, waitForElementByIdAndTap } from '../utils/utils' +import { enterPinUi, scrollIntoView, waitForElementById } from '../utils/utils' export default RestoreAccountOnboarding = () => { beforeEach(async () => { @@ -51,23 +51,23 @@ export default RestoreAccountOnboarding = () => { if (!walletFunded) { // case where account not funded yet. dismiss zero balance modal to continue with onboarding. - await waitForElementByIdAndTap('ConfirmUseAccountDialog/PrimaryAction') + await waitForElementById('ConfirmUseAccountDialog/PrimaryAction', { tap: true }) } if (!verifiedPhoneNumber) { // case where phone verification is required. skip it. - await waitForElementByIdAndTap('PhoneVerificationSkipHeader') + await waitForElementById('PhoneVerificationSkipHeader', { tap: true }) } // Choose your own adventure (CYA screen) - await waitForElementByIdAndTap('ChooseYourAdventure/Later') + await waitForElementById('ChooseYourAdventure/Later', { tap: true }) // verify that we land on the home screen await expect(element(by.id('HomeAction-Send'))).toBeVisible() // verify that the correct account was restored - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') - await waitForElementByIdAndTap('SettingsMenu/Address') + await waitForElementById('WalletHome/SettingsGearButton', { tap: true }) + await waitForElementById('SettingsMenu/Address', { tap: true }) await expect(element(by.text(walletAddress))).toBeVisible() } diff --git a/e2e/src/usecases/SecureSend.js b/e2e/src/usecases/SecureSend.js index 4fd7207b7b9..5669a4f12ca 100644 --- a/e2e/src/usecases/SecureSend.js +++ b/e2e/src/usecases/SecureSend.js @@ -10,8 +10,7 @@ import { fundWallet, quickOnboarding, scrollIntoView, - waitForElementByIdAndTap, - waitForElementId, + waitForElementById, } from '../utils/utils' const AMOUNT_TO_SEND = '0.01' @@ -32,17 +31,20 @@ export default SecureSend = () => { }) it('Send cUSD to phone number with multiple mappings', async () => { - await waitForElementByIdAndTap('HomeAction-Send', 30_000) - await waitForElementByIdAndTap('SendSelectRecipientSearchInput', 3000) + await waitForElementById('HomeAction-Send', { timeout: 30_000, tap: true }) + await waitForElementById('SendSelectRecipientSearchInput', { + timeout: 3000, + tap: true, + }) await element(by.id('SendSelectRecipientSearchInput')).replaceText( WALLET_MULTIPLE_VERIFIED_PHONE_NUMBER ) await element(by.id('RecipientItem')).tap() - await waitForElementByIdAndTap('SendOrInviteButton', 30_000) + await waitForElementById('SendOrInviteButton', { timeout: 30_000, tap: true }) // Use the last digits of the account to confirm the sender. - await waitForElementByIdAndTap('confirmAccountButton', 30_000) + await waitForElementById('confirmAccountButton', { timeout: 30_000, tap: true }) for (let index = 0; index < 4; index++) { const character = WALLET_MULTIPLE_VERIFIED_ADDRESS.charAt( WALLET_MULTIPLE_VERIFIED_ADDRESS.length - (4 - index) @@ -55,8 +57,11 @@ export default SecureSend = () => { await element(by.id('ConfirmAccountButton')).tap() // Select the currency - await waitForElementByIdAndTap('SendEnterAmount/TokenSelect', 30_000) - await waitForElementByIdAndTap('cUSDSymbol', 30_000) + await waitForElementById('SendEnterAmount/TokenSelect', { + timeout: 30_000, + tap: true, + }) + await waitForElementById('cUSDSymbol', { timeout: 30_000, tap: true }) // Enter the amount and review await element(by.id('SendEnterAmount/TokenAmountInput')).tap() @@ -69,7 +74,7 @@ export default SecureSend = () => { await enterPinUiIfNecessary() // Return to home screen. - await waitForElementId('HomeAction-Send', 30_000) + await waitForElementById('HomeAction-Send', { timeout: 30_000 }) }) }) } diff --git a/e2e/src/usecases/Send.js b/e2e/src/usecases/Send.js index d4afa9b7b45..c529210d56b 100644 --- a/e2e/src/usecases/Send.js +++ b/e2e/src/usecases/Send.js @@ -10,8 +10,7 @@ import { getDisplayAddress, isElementVisible, quickOnboarding, - waitForElementByIdAndTap, - waitForElementId, + waitForElementById, } from '../utils/utils' export default Send = () => { @@ -26,12 +25,15 @@ export default Send = () => { }) it('Then should navigate to send search input from home action', async () => { - await waitForElementByIdAndTap('HomeAction-Send', 30_000) - await waitForElementId('SendSelectRecipientSearchInput', 30_000) + await waitForElementById('HomeAction-Send', { timeout: 30_000, tap: true }) + await waitForElementById('SendSelectRecipientSearchInput', { timeout: 30_000 }) }) it('Then should be able to enter an address', async () => { - await waitForElementByIdAndTap('SendSelectRecipientSearchInput', 30_000) + await waitForElementById('SendSelectRecipientSearchInput', { + timeout: 30_000, + tap: true, + }) await element(by.id('SendSelectRecipientSearchInput')).replaceText(DEFAULT_RECIPIENT_ADDRESS) await element(by.id('SendSelectRecipientSearchInput')).tapReturnKey() await expect(element(by.text(recipientAddressDisplay)).atIndex(0)).toBeVisible() @@ -39,12 +41,12 @@ export default Send = () => { it('Then tapping a recipient should show send button', async () => { await element(by.text(recipientAddressDisplay)).atIndex(0).tap() - await waitForElementId('SendOrInviteButton', 30_000) + await waitForElementById('SendOrInviteButton', { timeout: 30_000 }) }) it('Then tapping send button should navigate to Send Enter Amount screen', async () => { await element(by.id('SendOrInviteButton')).tap() - await waitForElementId('SendEnterAmount/TokenAmountInput', 30_000) + await waitForElementById('SendEnterAmount/TokenAmountInput', { timeout: 30_000 }) }) it('Then should be able to change token', async () => { @@ -60,10 +62,16 @@ export default Send = () => { }) it('Then should be able to enter amount and navigate to review screen', async () => { - await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000) + await waitForElementById('SendEnterAmount/TokenAmountInput', { + timeout: 30_000, + tap: true, + }) await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.02') await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey() - await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000) + await waitForElementById('SendEnterAmount/ReviewButton', { + timeout: 30_000, + tap: true, + }) await isElementVisible('ConfirmButton') }) @@ -75,19 +83,25 @@ export default Send = () => { await element(by.id('BackChevron')).tap() await isElementVisible('SendEnterAmount/ReviewButton') await element(by.id('SendEnterAmount/TokenAmountInput')).tap() - await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000) + await waitForElementById('SendEnterAmount/TokenAmountInput', { + timeout: 30_000, + tap: true, + }) await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01') await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey() - await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000) + await waitForElementById('SendEnterAmount/ReviewButton', { + timeout: 30_000, + tap: true, + }) let amount = await element(by.id('SendAmount')).getAttributes() jestExpect(amount.text).toEqual('0.01 cEUR') }) it('Then should be able to send', async () => { - await waitForElementByIdAndTap('ConfirmButton', 30_000) + await waitForElementById('ConfirmButton', { timeout: 30_000, tap: true }) await enterPinUiIfNecessary() await expect(element(by.text('Transaction failed, please retry'))).not.toBeVisible() - await waitForElementId('HomeAction-Send', 30_000) + await waitForElementById('HomeAction-Send', { timeout: 30_000 }) }) }) @@ -97,7 +111,7 @@ export default Send = () => { }) it('Then should navigate to send search input from home action', async () => { - await waitForElementByIdAndTap('HomeAction-Send', 30_000) + await waitForElementById('HomeAction-Send', { timeout: 30_000, tap: true }) await waitFor(element(by.text(recipientAddressDisplay))) .toBeVisible() .withTimeout(10_000) @@ -105,7 +119,7 @@ export default Send = () => { it('Then should be able to click on recent recipient', async () => { await element(by.text(recipientAddressDisplay)).atIndex(0).tap() - await waitForElementId('SendEnterAmount/TokenAmountInput', 30_000) + await waitForElementById('SendEnterAmount/TokenAmountInput', { timeout: 30_000 }) }) it('Then should be able to choose token', async () => { @@ -115,10 +129,16 @@ export default Send = () => { }) it('Then should be able to enter amount and navigate to review screen', async () => { - await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000) + await waitForElementById('SendEnterAmount/TokenAmountInput', { + timeout: 30_000, + tap: true, + }) await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01') await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey() - await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000) + await waitForElementById('SendEnterAmount/ReviewButton', { + timeout: 30_000, + tap: true, + }) await isElementVisible('ConfirmButton') }) @@ -130,7 +150,7 @@ export default Send = () => { await element(by.id('ConfirmButton')).tap() await enterPinUiIfNecessary() await expect(element(by.text('Transaction failed, please retry'))).not.toBeVisible() - await waitForElementId('HomeAction-Send', 30_000) + await waitForElementById('HomeAction-Send', { timeout: 30_000 }) }) }) @@ -141,12 +161,15 @@ export default Send = () => { }) it('Then should navigate to send search input from home action', async () => { - await waitForElementByIdAndTap('HomeAction-Send', 30_000) - await waitForElementId('SendSelectRecipientSearchInput', 10_000) + await waitForElementById('HomeAction-Send', { timeout: 30_000, tap: true }) + await waitForElementById('SendSelectRecipientSearchInput', { timeout: 30_000 }) }) it('Then should be able to enter a phone number', async () => { - await waitForElementByIdAndTap('SendSelectRecipientSearchInput', 30_000) + await waitForElementById('SendSelectRecipientSearchInput', { + timeout: 30_000, + tap: true, + }) await element(by.id('SendSelectRecipientSearchInput')).typeText( WALLET_SINGLE_VERIFIED_PHONE_NUMBER ) @@ -156,12 +179,12 @@ export default Send = () => { it('Then tapping a recipient should show send button', async () => { await element(by.id('RecipientItem')).atIndex(0).tap() - await waitForElementId('SendOrInviteButton', 30_000) + await waitForElementById('SendOrInviteButton', { timeout: 30_000 }) }) it('Then tapping send button should navigate to Send Enter Amount screen', async () => { await element(by.id('SendOrInviteButton')).tap() - await waitForElementId('SendEnterAmount/TokenAmountInput', 30_000) + await waitForElementById('SendEnterAmount/TokenAmountInput', { timeout: 30_000 }) }) it('Then should be able to select token', async () => { @@ -171,10 +194,16 @@ export default Send = () => { }) it('Then should be able to enter amount and navigate to review screen', async () => { - await waitForElementByIdAndTap('SendEnterAmount/TokenAmountInput', 30_000) + await waitForElementById('SendEnterAmount/TokenAmountInput', { + timeout: 30_000, + tap: true, + }) await element(by.id('SendEnterAmount/TokenAmountInput')).replaceText('0.01') await element(by.id('SendEnterAmount/TokenAmountInput')).tapReturnKey() - await waitForElementByIdAndTap('SendEnterAmount/ReviewButton', 30_000) + await waitForElementById('SendEnterAmount/ReviewButton', { + timeout: 30_000, + tap: true, + }) await isElementVisible('ConfirmButton') }) @@ -186,7 +215,7 @@ export default Send = () => { await element(by.id('ConfirmButton')).tap() await enterPinUiIfNecessary() await expect(element(by.text('Transaction failed, please retry'))).not.toBeVisible() - await waitForElementId('HomeAction-Send', 30_000) + await waitForElementById('HomeAction-Send', { timeout: 30_000 }) }) }) } diff --git a/e2e/src/usecases/Settings.js b/e2e/src/usecases/Settings.js index ade5313a4e3..9b23b94f426 100644 --- a/e2e/src/usecases/Settings.js +++ b/e2e/src/usecases/Settings.js @@ -5,7 +5,7 @@ import { navigateToPreferences, navigateToProfile, scrollIntoView, - waitForElementByIdAndTap, + waitForElementById, } from '../utils/utils' const faker = require('@faker-js/faker') @@ -29,7 +29,7 @@ export default Settings = () => { .toBeVisible() .withTimeout(1000 * 10) await dismissBanners() - await waitForElementByIdAndTap('BackChevron') + await waitForElementById('BackChevron', { tap: true }) // TODO replace this with an ID selector await expect(element(by.text(`${randomName}`))).toBeVisible() }) diff --git a/e2e/src/usecases/Support.js b/e2e/src/usecases/Support.js index dd8ce1dfbd0..00fb0553b84 100644 --- a/e2e/src/usecases/Support.js +++ b/e2e/src/usecases/Support.js @@ -1,5 +1,5 @@ import { reloadReactNative } from '../utils/retries' -import { waitForElementByIdAndTap } from '../utils/utils' +import { waitForElementById } from '../utils/utils' export default Support = () => { beforeEach(async () => { @@ -32,8 +32,8 @@ export default Support = () => { } it('Send Message to Support', async () => { - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') - await waitForElementByIdAndTap('SettingsMenu/Help') + await waitForElementById('WalletHome/SettingsGearButton', { tap: true }) + await waitForElementById('SettingsMenu/Help', { tap: true }) await waitFor(element(by.id('SupportContactLink'))) .toBeVisible() .withTimeout(10000) diff --git a/e2e/src/usecases/WalletConnectV2.js b/e2e/src/usecases/WalletConnectV2.js index 82049e852a2..9a21853fc5e 100644 --- a/e2e/src/usecases/WalletConnectV2.js +++ b/e2e/src/usecases/WalletConnectV2.js @@ -15,7 +15,7 @@ import { sleep } from '../../../src/utils/sleep' import WALLET_ADDRESS from '../utils/consts' import { formatUri, utf8ToHex } from '../utils/encoding' import { launchApp } from '../utils/retries' -import { enterPinUiIfNecessary, waitForElementByIdAndTap } from '../utils/utils' +import { enterPinUiIfNecessary, waitForElementById } from '../utils/utils' import jestExpect from 'expect' @@ -357,7 +357,7 @@ export default WalletConnect = () => { }) it('Then should be able to disconnect a session', async () => { - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') + await waitForElementById('WalletHome/SettingsGearButton', { tap: true }) await element(by.id('SettingsMenu/ConnectedDapps')).tap() await element(by.text('Tap to Disconnect')).tap() await element(by.text('Disconnect')).tap() diff --git a/e2e/src/utils/dappList.js b/e2e/src/utils/dappList.js index 69e047e133b..c4677d0a3e6 100644 --- a/e2e/src/utils/dappList.js +++ b/e2e/src/utils/dappList.js @@ -1,5 +1,5 @@ import fetch from 'node-fetch' -import { waitForElementId } from './utils' +import { waitForElementById } from './utils' /** * From the home screen, navigate to the dapp explorer screen @@ -8,13 +8,9 @@ import { waitForElementId } from './utils' */ export async function navigateToDappList() { await device.disableSynchronization() - await waitForElementId('Hamburger') - await element(by.id('Hamburger')).tap() - await waitForElementId('dapps-explorer-icon') - await element(by.id('dapps-explorer-icon')).tap() - await waitFor(element(by.id('DAppsExplorerScreen/DappsList'))) - .toExist() - .withTimeout(10 * 1000) + await waitForElementById('Hamburger', { tap: true }) + await waitForElementById('dapps-explorer-icon', { tap: true }) + await waitForElementById('DAppsExplorerScreen/DappsList') await device.enableSynchronization() } @@ -22,10 +18,8 @@ export async function navigateToDappList() { * From the drawer navigate to home screen */ export async function navigateToHome() { - await waitForElementId('Hamburger') - await element(by.id('Hamburger')).tap() - await waitForElementId('Home') - await element(by.id('Home')).tap() + await waitForElementById('Hamburger', { tap: true }) + await waitForElementById('Home', { tap: true }) } /** diff --git a/e2e/src/utils/utils.js b/e2e/src/utils/utils.js index 2a573711ae4..d0791147c45 100644 --- a/e2e/src/utils/utils.js +++ b/e2e/src/utils/utils.js @@ -120,34 +120,24 @@ export async function isElementVisible(elementId, index) { return false } } -/** - * Wait for an element to be visible for at least set amount of time - * @param {string} elementId testID of the element to wait for - * @param {number} timeout timeout in milliseconds - */ -export async function waitForElementId(elementId, timeout = 10 * 1000) { + +export async function waitForElementById(testID, { timeout = 10000, index = 0, tap = false } = {}) { try { - await waitFor(element(by.id(elementId))) - .toBeVisible() - .withTimeout(timeout) + const elementMatcher = + index === 0 ? element(by.id(testID)) : element(by.id(testID)).atIndex(index) + + await waitFor(elementMatcher).toBeVisible().withTimeout(timeout) + + if (tap) { + index === 0 + ? await element(by.id(testID)).tap() + : await element(by.id(testID)).atIndex(index).tap() + } } catch { - throw new Error(`Element with testID '${elementId}' not found`) + throw new Error(`Element with testID '${testID}' not found`) } } -/** - * Wait for an element to be visible and then tap it - * @param {string} elementId testID of the element to wait for - * @param {number} timeout timeout in milliseconds - * @param {number} index index of the element to tap - */ -export async function waitForElementByIdAndTap(elementId, timeout = 10 * 1000, index = 0) { - await waitForElementId(elementId, timeout) - index === 0 - ? await element(by.id(elementId)).tap() - : await element(by.id(elementId)).atIndex(index).tap() -} - export async function quickOnboarding({ mnemonic = E2E_WALLET_MNEMONIC, cloudBackupEnabled = false, @@ -169,7 +159,12 @@ export async function quickOnboarding({ // Verify pin await enterPinUi() - if (cloudBackupEnabled) await waitForElementByIdAndTap('ImportSelect/Mnemonic') + if (cloudBackupEnabled) { + await waitForElementByText({ + text: 'From recovery phrase', + tap: true, + }) + } // Restore existing wallet await waitFor(element(by.id('connectingToCelo'))) @@ -177,7 +172,10 @@ export async function quickOnboarding({ .withTimeout(20000) // Input Wallet Backup Key - await waitForElementByIdAndTap('ImportWalletBackupKeyInputField') + await waitForElementById('ImportWalletBackupKeyInputField', { + tap: true, + }) + await element(by.id('ImportWalletBackupKeyInputField')).replaceText(mnemonic) if (device.getPlatform() === 'ios') { // On iOS, type one more space to workaround onChangeText not being triggered with replaceText above @@ -189,21 +187,25 @@ export async function quickOnboarding({ } await scrollIntoView('Restore', 'ImportWalletKeyboardAwareScrollView') - await waitForElementByIdAndTap('ImportWalletButton') + await waitForElementById('ImportWalletButton', { + tap: true, + }) // Wait for the wallet to restored await sleep(5 * 1000) try { // case where account not funded yet. continue with onboarding. - await waitForElementByIdAndTap('ConfirmUseAccountDialog/PrimaryAction') + await waitForElementById('ConfirmUseAccountDialog/PrimaryAction', { + tap: true, + }) } catch {} // this onboarding step is bypassed for already verified wallets try { - // Verify Education - await waitForElementId('PhoneVerificationSkipHeader') - // Skip - await element(by.id('PhoneVerificationSkipHeader')).tap() + // Skip Phone Verification + await waitForElementById('PhoneVerificationSkipHeader', { + tap: true, + }) } catch { console.log( 'Error trying to skip phone verification step during onboarding, likely due to wallet already being verified' @@ -212,10 +214,12 @@ export async function quickOnboarding({ // Choose your own adventure (CYA screen) if (stopOnCYA) { - await waitForElementId('ChooseYourAdventure/Later') + await waitForElementById('ChooseYourAdventure/Later') return } - await waitForElementByIdAndTap('ChooseYourAdventure/Later') + await waitForElementById('ChooseYourAdventure/Later', { + tap: true, + }) // Assert on Wallet Home Screen await expect(element(by.id('HomeAction-Send'))).toBeVisible() @@ -412,18 +416,30 @@ export async function fundWallet(senderPrivateKey, recipientAddress, stableToken } export async function navigateToSecurity() { - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') - await waitForElementByIdAndTap('SettingsMenu/Security') + await waitForElementById('WalletHome/SettingsGearButton', { + tap: true, + }) + await waitForElementById('SettingsMenu/Security', { + tap: true, + }) } export async function navigateToProfile() { - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') - await waitForElementByIdAndTap('SettingsMenu/Profile') + await waitForElementById('WalletHome/SettingsGearButton', { + tap: true, + }) + await waitForElementById('SettingsMenu/Profile', { + tap: true, + }) } export async function navigateToPreferences() { - await waitForElementByIdAndTap('WalletHome/SettingsGearButton') - await waitForElementByIdAndTap('SettingsMenu/Preferences') + await waitForElementById('WalletHome/SettingsGearButton', { + tap: true, + }) + await waitForElementById('SettingsMenu/Preferences', { + tap: true, + }) } export const getDisplayAddress = (address) => { diff --git a/ios/MobileStack.xcodeproj/project.pbxproj b/ios/MobileStack.xcodeproj/project.pbxproj index 08c7890edce..1d26139c50b 100644 --- a/ios/MobileStack.xcodeproj/project.pbxproj +++ b/ios/MobileStack.xcodeproj/project.pbxproj @@ -767,7 +767,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = NO; - CURRENT_PROJECT_VERSION = 165; + CURRENT_PROJECT_VERSION = 166; ENABLE_BITCODE = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; "EXCLUDED_ARCHS[sdk=iphonesimulator*]" = "arm64 "; @@ -794,7 +794,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", ); IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.100.0; + MARKETING_VERSION = 1.101.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; OTHER_CFLAGS = "$(inherited)"; @@ -828,7 +828,7 @@ CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; "CODE_SIGN_IDENTITY[sdk=iphoneos*]" = "iPhone Developer"; COPY_PHASE_STRIP = YES; - CURRENT_PROJECT_VERSION = 165; + CURRENT_PROJECT_VERSION = 166; ENABLE_BITCODE = NO; ENABLE_NS_ASSERTIONS = NO; ENABLE_STRICT_OBJC_MSGSEND = YES; @@ -853,7 +853,7 @@ "${PODS_CONFIGURATION_BUILD_DIR}/React-graphics/React_graphics.framework/Headers/react/renderer/graphics/platform/ios", ); IPHONEOS_DEPLOYMENT_TARGET = 15.0; - MARKETING_VERSION = 1.100.0; + MARKETING_VERSION = 1.101.0; MTL_ENABLE_DEBUG_INFO = NO; OTHER_CFLAGS = "$(inherited)"; OTHER_CPLUSPLUSFLAGS = "$(inherited)"; diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 1e8c8a7a994..2f44bc4f100 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -563,7 +563,7 @@ PODS: - React-jsinspector (0.72.15) - React-logger (0.72.15): - glog - - react-native-adjust (5.0.2): + - react-native-adjust (5.0.3): - Adjust (= 5.0.1) - React-Core - react-native-camera (4.2.1): @@ -596,7 +596,7 @@ PODS: - react-native-pager-view (6.4.1): - RCT-Folly (= 2021.07.22.00) - React-Core - - react-native-quick-crypto (0.7.7): + - react-native-quick-crypto (0.7.10): - OpenSSL-Universal - RCT-Folly (= 2021.07.22.00) - React @@ -1268,7 +1268,7 @@ SPEC CHECKSUMS: React-jsiexecutor: d3eef5ddc78eeb9f0d02bed657a7f41d4910b966 React-jsinspector: b86a8abae760c28d69366bbc1d991561e51341ed React-logger: ed7c9e01e58529065e7da6bf8318baf15024283e - react-native-adjust: 6a73063a62fb0f21dd2220f15b315c1e323268b0 + react-native-adjust: 59b523d8ef1dc2c0516fb7120ed04cdfc5510a90 react-native-camera: 3eae183c1d111103963f3dd913b65d01aef8110f react-native-compat: 24dedfbd7faba258560048606bc0fe9e73f7d2a3 react-native-config: 8f7283449bbb048902f4e764affbbf24504454af @@ -1279,7 +1279,7 @@ SPEC CHECKSUMS: react-native-launch-arguments: 5f41e0abf88a15e3c5309b8875d6fd5ac43df49d react-native-netinfo: f0a9899081c185db1de5bb2fdc1c88c202a059ac react-native-pager-view: c65514a81a384c3ce244b5bd87167761740308d3 - react-native-quick-crypto: e254783f9681542c182f9439f91139137e3b9e48 + react-native-quick-crypto: e7ca8ccd138d8e54b9be3bc686fcc4e8c53866d7 react-native-restart: 7595693413fe3ca15893702f2c8306c62a708162 react-native-safe-area-context: 141eca0fd4e4191288dfc8b96a7c7e1c2983447a react-native-shake: 0c36371dd63019afa68890ccc65a442ee21b4dde diff --git a/locales/base/translation.json b/locales/base/translation.json index 0fc566eac61..d489b8bbf93 100644 --- a/locales/base/translation.json +++ b/locales/base/translation.json @@ -2783,9 +2783,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "You Need {{tokenSymbol}} on {{tokenNetwork}} to Deposit", + "depositTitle": "Deposit to pool", "crossChainAlternativeDescription": "If you don’t want to use your tokens on {{tokenNetwork}}, choose an option below. You’ll need to return to complete your pool deposit later.", "beforeYouCanDepositTitle": "Before you can deposit...", "beforeYouCanDepositDescription": "You’ll need to add one of the pool tokens. Once added, you’ll need to return to complete your pool deposit.", + "beforeYouCanDepositDescriptionV1_101": "You’ll need to add {{tokenSymbol}} on {{tokenNetwork}}. Once added, you’ll need to return to complete your pool deposit.", "action": { "swapAndDeposit": "Swap & Deposit", "swapAndDepositDescription": "Choose any token on {{tokenNetwork}}. We’ll swap and deposit it simultaneously for you.", @@ -2796,7 +2798,10 @@ "add": "Buy", "addDescription": "Buy {{tokenSymbol}} on {{tokenNetwork}} using one of our trusted providers", "transfer": "Transfer", - "transferDescription": "Use any {{tokenNetwork}} compatible wallet or exchange to deposit {{tokenSymbol}}" + "transferDescription": "Use any {{tokenNetwork}} compatible wallet or exchange to deposit {{tokenSymbol}}", + "addMore": "Buy more {{tokenSymbol}}", + "deposit": "Deposit your {{tokenSymbol}}", + "depositDescription": "You have {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/de/translation.json b/locales/de/translation.json index 819aa16e044..de0b030853b 100644 --- a/locales/de/translation.json +++ b/locales/de/translation.json @@ -2781,9 +2781,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Du brauchst {{tokenSymbol}} auf {{tokenNetwork}} zum Einzahlen", + "depositTitle": "Einzahlung in den Pool", "crossChainAlternativeDescription": "Wenn du deine Token nicht auf {{tokenNetwork}} verwenden möchtest, wähle eine der untenstehenden Optionen. Du musst später zurückkehren, um deine Pool-Einzahlung abzuschließen.", "beforeYouCanDepositTitle": "Bevor du eine Einzahlung vornehmen kannst ...", "beforeYouCanDepositDescription": "Du musst einen der Pool-Token hinzufügen. Sobald du es hinzugefügt hast, musst du zurückkehren, um deine Pool-Einzahlung abzuschließen.", + "beforeYouCanDepositDescriptionV1_101": "Du musst {{tokenSymbol}} auf {{tokenNetwork}}hinzufügen. Sobald du sie hinzugefügt hast, musst du zurückkehren, um deine Pool-Einzahlung abzuschließen.", "action": { "swapAndDeposit": "Tausch und Einzahlung", "swapAndDepositDescription": "Wähle einen beliebigen Token auf {{tokenNetwork}}. Wir tauschen und hinterlegen ihn gleichzeitig für dich.", @@ -2794,7 +2796,10 @@ "add": "Kaufen", "addDescription": "Kaufe {{tokenSymbol}} auf {{tokenNetwork}} über einen unserer vertrauenswürdigen Anbieter", "transfer": "Überweisung", - "transferDescription": "Verwende jede {{tokenNetwork}} kompatible Wallet oder Börse, um {{tokenSymbol}} einzuzahlen" + "transferDescription": "Verwende jede {{tokenNetwork}} kompatible Wallet oder Börse, um {{tokenSymbol}} einzuzahlen", + "addMore": "Mehr kaufen {{tokenSymbol}}", + "deposit": "Hinterlege deine {{tokenSymbol}}", + "depositDescription": "Du hast {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/en-US/translation.json b/locales/en-US/translation.json index 3e8c91818b8..a8bb76585b6 100644 --- a/locales/en-US/translation.json +++ b/locales/en-US/translation.json @@ -2781,9 +2781,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "You Need {{tokenSymbol}} on {{tokenNetwork}} to Deposit", + "depositTitle": "Deposit to pool", "crossChainAlternativeDescription": "If you don’t want to use your tokens on {{tokenNetwork}}, choose an option below. You’ll need to return to complete your pool deposit later.", "beforeYouCanDepositTitle": "Before you can deposit...", "beforeYouCanDepositDescription": "You’ll need to add one of the pool tokens. Once added, you’ll need to return to complete your pool deposit.", + "beforeYouCanDepositDescriptionV1_101": "You’ll need to add {{tokenSymbol}} on {{tokenNetwork}}. Once added, you’ll need to return to complete your pool deposit.", "action": { "swapAndDeposit": "Swap & Deposit", "swapAndDepositDescription": "Choose any token on {{tokenNetwork}}. We’ll swap and deposit it simultaneously for you.", @@ -2794,7 +2796,10 @@ "add": "Buy", "addDescription": "Buy {{tokenSymbol}} on {{tokenNetwork}} using one of our trusted providers", "transfer": "Transfer", - "transferDescription": "Use any {{tokenNetwork}} compatible wallet or exchange to deposit {{tokenSymbol}}" + "transferDescription": "Use any {{tokenNetwork}} compatible wallet or exchange to deposit {{tokenSymbol}}", + "addMore": "Buy more {{tokenSymbol}}", + "deposit": "Deposit your {{tokenSymbol}}", + "depositDescription": "You have {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/es-419/translation.json b/locales/es-419/translation.json index 53419d9ea51..a10dd37d5b4 100644 --- a/locales/es-419/translation.json +++ b/locales/es-419/translation.json @@ -2781,9 +2781,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Necesitas {{tokenSymbol}} en {{tokenNetwork}} para depositar", + "depositTitle": "Depósito a la piscina", "crossChainAlternativeDescription": "Si no quieres usar tus tokens en {{tokenNetwork}}, elige una opción a continuación. Tendrás que volver para completar tu depósito en el pool más tarde.", "beforeYouCanDepositTitle": "Antes de que puedas depositar...", "beforeYouCanDepositDescription": "Tendrás que agregarr uno de los tokens del pool. Una vez agregado, tendrás que volver para completar el depósito en el pool.", + "beforeYouCanDepositDescriptionV1_101": "Tendrás que añadir {{tokenSymbol}} en {{tokenNetwork}}. Una vez añadido, tendrás que volver para completar el depósito de tu piscina.", "action": { "swapAndDeposit": "Intercambiar y depositar", "swapAndDepositDescription": "Elige cualquier token en {{tokenNetwork}}. Lo cambiaremos y lo depositaremos simultáneamente por ti.", @@ -2794,7 +2796,10 @@ "add": "Comprar", "addDescription": "Compra {{tokenSymbol}} en {{tokenNetwork}} a uno de nuestros proveedores de confianza.", "transfer": "Transferir", - "transferDescription": "Usa cualquier wallet o exchange compatible con {{tokenNetwork}} para depositar {{tokenSymbol}}." + "transferDescription": "Usa cualquier wallet o exchange compatible con {{tokenNetwork}} para depositar {{tokenSymbol}}.", + "addMore": "Compra más {{tokenSymbol}}", + "deposit": "Deposita tu {{tokenSymbol}}", + "depositDescription": "Tienes {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/fr-FR/translation.json b/locales/fr-FR/translation.json index f905d6751fa..cb4e133295f 100644 --- a/locales/fr-FR/translation.json +++ b/locales/fr-FR/translation.json @@ -2781,9 +2781,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Vous avez besoin de {{tokenSymbol}} sur {{tokenNetwork}} pour faire un dépôt", + "depositTitle": "Dépôt à la piscine", "crossChainAlternativeDescription": "Si vous ne voulez pas utiliser vos jetons sur {{tokenNetwork}}, choisissez une option ci-dessous. Vous devrez revenir pour compléter votre dépôt de pool plus tard.", "beforeYouCanDepositTitle": "Avant de pouvoir effectuer un dépôt...", "beforeYouCanDepositDescription": "Vous devrez ajouter un des jetons du pool. Une fois ajouté, vous devrez revenir pour compléter votre dépôt de pool.", + "beforeYouCanDepositDescriptionV1_101": "Tu devras ajouter {{tokenSymbol}} sur {{tokenNetwork}}. Une fois ajouté, tu devras revenir pour compléter ton dépôt de piscine.", "action": { "swapAndDeposit": "Échange et dépôt", "swapAndDepositDescription": "Choisissez n'importe quel jeton sur {{tokenNetwork}}. Nous le changerons et le déposerons simultanément pour vous.", @@ -2794,7 +2796,10 @@ "add": "Acheter", "addDescription": "Achetez des {{tokenSymbol}} sur {{tokenNetwork}} en utilisant un de nos fournisseurs de confiance", "transfer": "Transférer", - "transferDescription": "Utilisez n'importe quel portefeuille compatible {{tokenNetwork}} ou échangez pour déposer des {{tokenSymbol}}" + "transferDescription": "Utilisez n'importe quel portefeuille compatible {{tokenNetwork}} ou échangez pour déposer des {{tokenSymbol}}", + "addMore": "Acheter plus de {{tokenSymbol}}", + "deposit": "Dépose ton {{tokenSymbol}}", + "depositDescription": "Tu as {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/it-IT/translation.json b/locales/it-IT/translation.json index d5536461f59..2a003bed3c1 100644 --- a/locales/it-IT/translation.json +++ b/locales/it-IT/translation.json @@ -2781,9 +2781,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Hai bisogno di {{tokenSymbol}} su {{tokenNetwork}} per depositare", + "depositTitle": "Deposito in piscina", "crossChainAlternativeDescription": "Se non vuoi usare i tuoi token su {{tokenNetwork}}, scegli un'opzione qui sotto. Devi tornare per completare il deposito nel pool in un secondo momento.", "beforeYouCanDepositTitle": "Prima di poter depositare...", "beforeYouCanDepositDescription": "Devi aggiungere uno dei token del pool. Una volta aggiunto, devi tornare per completare il deposito nel pool.", + "beforeYouCanDepositDescriptionV1_101": "Dovrai aggiungere {{tokenSymbol}} su {{tokenNetwork}}. Una volta aggiunto, dovrai tornare per completare il deposito della piscina.", "action": { "swapAndDeposit": "Scambio e deposito", "swapAndDepositDescription": "Scegli qualsiasi token su {{tokenNetwork}}. Lo scambieremo e lo depositeremo simultaneamente per te.", @@ -2794,7 +2796,10 @@ "add": "Acquista", "addDescription": "Acquista {{tokenSymbol}} su {{tokenNetwork}} utilizzando uno dei nostri fornitori di fiducia", "transfer": "Trasferisci", - "transferDescription": "Utilizza qualsiasi wallet o exchange compatibile con {{tokenNetwork}} per depositare {{tokenSymbol}}" + "transferDescription": "Utilizza qualsiasi wallet o exchange compatibile con {{tokenNetwork}} per depositare {{tokenSymbol}}", + "addMore": "Acquista più {{tokenSymbol}}", + "deposit": "Deposita il tuo {{tokenSymbol}}", + "depositDescription": "Hai {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/pl-PL/translation.json b/locales/pl-PL/translation.json index 93edf92f7d6..b53667797d2 100644 --- a/locales/pl-PL/translation.json +++ b/locales/pl-PL/translation.json @@ -2785,9 +2785,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Aby dokonać wpłaty, potrzebny jest {{tokenSymbol}} w {{tokenNetwork}}", + "depositTitle": "Depozyt do puli", "crossChainAlternativeDescription": "Jeśli nie chcesz użyć swoich tokenów w {{tokenNetwork}}, wybierz poniższą opcję. Trzeba będzie tu wrócić, aby później sfinalizować wpłatę do puli.", "beforeYouCanDepositTitle": "Zanim będzie można dokonać wpłaty...", "beforeYouCanDepositDescription": "Musisz dodać jeden z tokenów puli. Po dodaniu musisz tu wrócić, aby sfinalizować wpłatę do puli.", + "beforeYouCanDepositDescriptionV1_101": "Musisz dodać stronę {{tokenSymbol}} na {{tokenNetwork}}. Po dodaniu musisz wrócić, aby uzupełnić depozyt za basen.", "action": { "swapAndDeposit": "Wymiana i wpłata", "swapAndDepositDescription": "Wybierz dowolny token w {{tokenNetwork}}. Wymienimy go i jednocześnie wpłacimy za Ciebie.", @@ -2798,7 +2800,10 @@ "add": "Kup", "addDescription": "Kup {{tokenSymbol}} w {{tokenNetwork}}, korzystając z usług jednego z naszych zaufanych dostawców", "transfer": "Transfer", - "transferDescription": "Użyj dowolnego portfela lub giełdy kompatybilnej z {{tokenNetwork}}, aby wpłacić {{tokenSymbol}}" + "transferDescription": "Użyj dowolnego portfela lub giełdy kompatybilnej z {{tokenNetwork}}, aby wpłacić {{tokenSymbol}}", + "addMore": "Kup więcej {{tokenSymbol}}", + "deposit": "Złóż depozyt na {{tokenSymbol}}", + "depositDescription": "Masz {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/pt-BR/translation.json b/locales/pt-BR/translation.json index 489da6b8f50..53b4d34a617 100644 --- a/locales/pt-BR/translation.json +++ b/locales/pt-BR/translation.json @@ -2781,9 +2781,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Você precisa de {{tokenSymbol}} da {{tokenNetwork}} para depositar", + "depositTitle": "Depósito para piscina", "crossChainAlternativeDescription": "Se não quiser usar seus tokens da {{tokenNetwork}}, escolha uma opção abaixo. Você precisará retornar para concluir o depósito no pool mais tarde.", "beforeYouCanDepositTitle": "Antes de poder depositar...", "beforeYouCanDepositDescription": "Você precisará adicionar um dos tokens do pool. Depois de adicionar, você precisará retornar para concluir o depósito no pool.", + "beforeYouCanDepositDescriptionV1_101": "Você precisará adicionar {{tokenSymbol}} em {{tokenNetwork}}. Depois de adicionado, você precisará retornar para concluir o depósito da piscina.", "action": { "swapAndDeposit": "Trocar e depositar", "swapAndDepositDescription": "Escolha qualquer token da {{tokenNetwork}}. Faremos a troca e o depósito simultaneamente para você.", @@ -2794,7 +2796,10 @@ "add": "Comprar", "addDescription": "Compre {{tokenSymbol}} na {{tokenNetwork}} usando um dos nossos provedores confiáveis", "transfer": "Transferir", - "transferDescription": "Use qualquer carteira ou exchange compatível com {{tokenNetwork}} para depositar {{tokenSymbol}}" + "transferDescription": "Use qualquer carteira ou exchange compatível com {{tokenNetwork}} para depositar {{tokenSymbol}}", + "addMore": "Compre mais {{tokenSymbol}}", + "deposit": "Deposite seu {{tokenSymbol}}", + "depositDescription": "Você tem {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/ru-RU/translation.json b/locales/ru-RU/translation.json index f04fcc31eb3..ac9a1aa4f91 100644 --- a/locales/ru-RU/translation.json +++ b/locales/ru-RU/translation.json @@ -2785,9 +2785,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Для пополнения счета потребуется {{tokenSymbol}} в {{tokenNetwork}}", + "depositTitle": "Депозит в бассейн", "crossChainAlternativeDescription": "Если вы не хотите использовать свои токены в {{tokenNetwork}}, выберите вариант ниже. Потребуется вернуться, чтобы завершить пополнение счета в пуле позже.", "beforeYouCanDepositTitle": "Для пополнения счета...", "beforeYouCanDepositDescription": "Вам потребуется добавить один из токенов пула. После добавления потребуется вернуться, чтобы завершить пополнение счета в пуле.", + "beforeYouCanDepositDescriptionV1_101": "Тебе нужно будет добавить {{tokenSymbol}} на сайте {{tokenNetwork}}. После добавления тебе нужно будет вернуться, чтобы завершить депозит за бассейн.", "action": { "swapAndDeposit": "Обмен и пополнение счета", "swapAndDepositDescription": "Выберите любой токен в {{tokenNetwork}}. Мы сразу выполним обмен и пополнение счета.", @@ -2798,7 +2800,10 @@ "add": "Купить", "addDescription": "Купите {{tokenSymbol}} в {{tokenNetwork}} с помощью одного из доверенных поставщиков", "transfer": "Перевод", - "transferDescription": "Используйте совместимый с {{tokenNetwork}} кошелек или криптобиржу для депонирования {{tokenSymbol}}" + "transferDescription": "Используйте совместимый с {{tokenNetwork}} кошелек или криптобиржу для депонирования {{tokenSymbol}}", + "addMore": "Покупай больше {{tokenSymbol}}", + "deposit": "Пополни свой счет {{tokenSymbol}}", + "depositDescription": "У тебя есть {{amount}} {{tokenSymbol}}." } } }, diff --git a/locales/th-TH/translation.json b/locales/th-TH/translation.json index 9db1bb10075..bd6e76dc86d 100644 --- a/locales/th-TH/translation.json +++ b/locales/th-TH/translation.json @@ -2779,9 +2779,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "คุณจำเป็นต้องมี {{tokenSymbol}} บน {{tokenNetwork}} จึงจะฝากเงินได้", + "depositTitle": "Deposit to pool", "crossChainAlternativeDescription": "หากคุณไม่ต้องการใช้โทเค็นของคุณบน {{tokenNetwork}} ให้เลือกตัวเลือกด้านล่างนี้ ทั้งนี้ คุณจะต้องกลับมาฝากเงินเข้าพูลในภายหลัง", "beforeYouCanDepositTitle": "ก่อนที่คุณจะสามารถฝากเงินได้...", "beforeYouCanDepositDescription": "คุณจะต้องเพิ่มโทเค็นพูลหนึ่งรายการ เมื่อเพิ่มแล้ว คุณจะต้องกลับไปเพื่อทำการฝากเงินเข้าพูลให้เสร็จสมบูรณ์", + "beforeYouCanDepositDescriptionV1_101": "You’ll need to add {{tokenSymbol}} on {{tokenNetwork}}. Once added, you’ll need to return to complete your pool deposit.", "action": { "swapAndDeposit": "สว็อปและฝากเงิน", "swapAndDepositDescription": "เลือกโทเค็นใดโทเค็นหนึ่งบน {{tokenNetwork}} เราจะสว็อปและฝากเงินให้คุณในเวลาเดียวกัน", @@ -2792,7 +2794,10 @@ "add": "ซื้อ", "addDescription": "ซื้อ {{tokenSymbol}} บน {{tokenNetwork}} ผ่านหนึ่งในผู้ให้บริการที่น่าเชื่อถือของเรา", "transfer": "โอน", - "transferDescription": "ใช้กระเป๋าเงินหรือกระดานแลกเปลี่ยนที่ใช้งานร่วมกันได้กับ {{tokenNetwork}} เพื่อฝาก {{tokenSymbol}}" + "transferDescription": "ใช้กระเป๋าเงินหรือกระดานแลกเปลี่ยนที่ใช้งานร่วมกันได้กับ {{tokenNetwork}} เพื่อฝาก {{tokenSymbol}}", + "addMore": "Buy more {{tokenSymbol}}", + "deposit": "Deposit your {{tokenSymbol}}", + "depositDescription": "You have {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/tr-TR/translation.json b/locales/tr-TR/translation.json index 62bf469a164..05a44a1f3f8 100644 --- a/locales/tr-TR/translation.json +++ b/locales/tr-TR/translation.json @@ -2781,9 +2781,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Yatırma İşlemi için {{tokenNetwork}} ağında {{tokenSymbol}} tokenine sahip olmalısınız", + "depositTitle": "Havuz için depozito", "crossChainAlternativeDescription": "{{tokenNetwork}} ağındaki tokenlerinizi kullanmaz istemiyorsanız aşağıdaki seçeneklerden birini seçin. Havuza yatırma işleminizi tamamlamak için daha sonra geri dönmeniz gerekecektir.", "beforeYouCanDepositTitle": "Para yatırmadan önce...", "beforeYouCanDepositDescription": "Havuz tokenlerinden birini eklemeniz gereklidir. Token eklendikten sonra havuza para yatırma işlemini tamamlamak için geri dönmeniz gerekir.", + "beforeYouCanDepositDescriptionV1_101": "{{tokenSymbol}} adresini {{tokenNetwork}}adresine eklemeniz gerekmektedir. Eklendikten sonra, havuz depozitonuzu tamamlamak için geri dönmeniz gerekecektir.", "action": { "swapAndDeposit": "Swap ve Para Yatırma", "swapAndDepositDescription": "{{tokenNetwork}} ağındaki herhangi bir tokeni seçin. Seçtiğiniz tokeni sizin için anında swap edip yatırma işleminizi tamamlayacağız.", @@ -2794,7 +2796,10 @@ "add": "Satın Al", "addDescription": "Güvenilir sağlayıcılarımızdan birini kullanarak {{tokenNetwork}} ağında {{tokenSymbol}} satın alın", "transfer": "Transfer Et", - "transferDescription": "{{tokenSymbol}} yatırmak için {{tokenNetwork}} uyumlu cüzdan veya borsa kullanın" + "transferDescription": "{{tokenSymbol}} yatırmak için {{tokenNetwork}} uyumlu cüzdan veya borsa kullanın", + "addMore": "Daha fazla satın al {{tokenSymbol}}", + "deposit": "{{tokenSymbol}}adresinizi yatırın", + "depositDescription": "{{amount}} {{tokenSymbol}}adresiniz var." } } }, diff --git a/locales/uk-UA/translation.json b/locales/uk-UA/translation.json index 4f699c8b691..ec3762e177d 100644 --- a/locales/uk-UA/translation.json +++ b/locales/uk-UA/translation.json @@ -2785,9 +2785,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Вам потрібен {{tokenSymbol}} в {{tokenNetwork}} для внесення депозиту", + "depositTitle": "Депозит в пул", "crossChainAlternativeDescription": "Якщо ви не хочете використовувати свої токени в мережі {{tokenNetwork}}, виберіть один із варіантів нижче. Вам треба буде повернутися пізніше, щоб внести кошти в пул.", "beforeYouCanDepositTitle": "Перш ніж внести депозит...", "beforeYouCanDepositDescription": "Потрібно додати один із токенів пулу. Після додавання вам треба буде повернутися, щоб внести кошти в пул.", + "beforeYouCanDepositDescriptionV1_101": "Вам потрібно буде додати {{tokenSymbol}} на {{tokenNetwork}}. Після додавання вам потрібно буде повернутися, щоб поповнити свій депозит у пулі.", "action": { "swapAndDeposit": "Обмін і внесення депозиту", "swapAndDepositDescription": "Виберіть будь-який токен у мережі {{tokenNetwork}}. Ми одночасно обміняємо його та внесемо депозит за вас.", @@ -2798,7 +2800,10 @@ "add": "Купити", "addDescription": "Купіть {{tokenSymbol}} у мережі {{tokenNetwork}} через одного з наших довірених постачальників", "transfer": "Переказати", - "transferDescription": "Скористайтеся будь-яким гаманцем, сумісним із мережею {{tokenNetwork}} або виконайте обмін, щоб внести {{tokenSymbol}}" + "transferDescription": "Скористайтеся будь-яким гаманцем, сумісним із мережею {{tokenNetwork}} або виконайте обмін, щоб внести {{tokenSymbol}}", + "addMore": "Купити більше {{tokenSymbol}}", + "deposit": "Поповніть свій рахунок на {{tokenSymbol}}", + "depositDescription": "У вас є {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/vi-VN/translation.json b/locales/vi-VN/translation.json index a5f180e02da..ec5c1cb872e 100644 --- a/locales/vi-VN/translation.json +++ b/locales/vi-VN/translation.json @@ -2779,9 +2779,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "Bạn cần {{tokenSymbol}} trên {{tokenNetwork}} để nạp", + "depositTitle": "Deposit to pool", "crossChainAlternativeDescription": "Nếu bạn không muốn sử dụng token trên {{tokenNetwork}}, hãy chọn một lựa chọn bên dưới. Bạn sẽ phải quay lại để hoàn tất giao dịch nạp vào bể sau đó.", "beforeYouCanDepositTitle": "Trước khi bạn có thể nạp...", "beforeYouCanDepositDescription": "Bạn sẽ cần thêm một trong các token bể. Sau khi thêm, bạn phải quay lại để hoàn tất giao dịch nạp vào bể.", + "beforeYouCanDepositDescriptionV1_101": "You’ll need to add {{tokenSymbol}} on {{tokenNetwork}}. Once added, you’ll need to return to complete your pool deposit.", "action": { "swapAndDeposit": "Trao đổi và nạp", "swapAndDepositDescription": "Chọn token bất kỳ trên {{tokenNetwork}}. Chúng tôi sẽ trao đổi và nạp đồng thời token đó cho bạn.", @@ -2792,7 +2794,10 @@ "add": "Mua", "addDescription": "Mua {{tokenSymbol}} trên {{tokenNetwork}} qua một trong những nhà cung cấp uy tín của chúng tôi", "transfer": "Chuyển tiền", - "transferDescription": "Sử dụng bất kỳ ví hoặc sàn giao dịch nào tương thích với {{tokenNetwork}} để nạp {{tokenSymbol}}" + "transferDescription": "Sử dụng bất kỳ ví hoặc sàn giao dịch nào tương thích với {{tokenNetwork}} để nạp {{tokenSymbol}}", + "addMore": "Buy more {{tokenSymbol}}", + "deposit": "Deposit your {{tokenSymbol}}", + "depositDescription": "You have {{amount}} {{tokenSymbol}}" } } }, diff --git a/locales/zh-CN/translation.json b/locales/zh-CN/translation.json index 7819aa9a3ff..93c3a0c9c44 100644 --- a/locales/zh-CN/translation.json +++ b/locales/zh-CN/translation.json @@ -2779,9 +2779,11 @@ }, "beforeDepositBottomSheet": { "youNeedTitle": "您需要在 {{tokenNetwork}} 上持有 {{tokenSymbol}} 才可进行存款", + "depositTitle": "游泳池押金", "crossChainAlternativeDescription": "如果您不想使用 {{tokenNetwork}} 上的代币,请选择以下选项。您需要稍后返回,继续完成资金池存款操作。", "beforeYouCanDepositTitle": "在您进行存款之前……", "beforeYouCanDepositDescription": "您需要添加某一资金池的代币。成功添加代币后,您需要返回继续完成资金池存款操作。", + "beforeYouCanDepositDescriptionV1_101": "您需要在 {{tokenNetwork}}上添加 {{tokenSymbol}} 。添加后,您需要返回以完成泳池押金。", "action": { "swapAndDeposit": "交换与存款", "swapAndDepositDescription": "请选择 {{tokenNetwork}} 上的任何代币。我们将同步为您进行交换和存款。", @@ -2792,7 +2794,10 @@ "add": "买入", "addDescription": "使用我们值得信赖的提供商之一,在 {{tokenNetwork}} 上购买 {{tokenSymbol}}", "transfer": "转账", - "transferDescription": "使用任何与 {{tokenNetwork}} 兼容的钱包或交易所,来存入 {{tokenSymbol}}" + "transferDescription": "使用任何与 {{tokenNetwork}} 兼容的钱包或交易所,来存入 {{tokenSymbol}}", + "addMore": "购买更多 {{tokenSymbol}}", + "deposit": "存款 {{tokenSymbol}}", + "depositDescription": "您有 {{amount}} {{tokenSymbol}}" } } }, diff --git a/package.json b/package.json index 524f05169a6..d0ea1f55d51 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@valora/wallet", - "version": "1.100.0", + "version": "1.101.0", "author": "Valora Inc", "license": "Apache-2.0", "private": true, @@ -73,8 +73,8 @@ "dependencies": { "@badrap/result": "~0.2.13", "@crowdin/ota-client": "^0.7.0", - "@fiatconnect/fiatconnect-sdk": "^0.5.62", - "@fiatconnect/fiatconnect-types": "^13.3.8", + "@fiatconnect/fiatconnect-sdk": "^0.5.66", + "@fiatconnect/fiatconnect-types": "^13.3.10", "@gorhom/bottom-sheet": "^4.6.4", "@json-rpc-tools/utils": "^1.7.6", "@noble/secp256k1": "^1.7.1", @@ -122,20 +122,20 @@ "fp-ts": "2.16.9", "futoin-hkdf": "^1.5.3", "fuzzysort": "^3.1.0", - "google-libphonenumber": "^3.2.39", + "google-libphonenumber": "^3.2.40", "i18next": "^23.16.8", "ibantools": "^4.5.1", "intl-pluralrules": "^2.0.1", - "io-ts": "2.2.21", + "io-ts": "2.2.22", "is-ip": "^3.1.0", "jwt-decode": "^4.0.0", "lodash": "^4.17.21", "lottie-react-native": "^5.1.6", "react": "18.3.1", "react-async-hook": "^4.0.0", - "react-i18next": "^15.1.3", + "react-i18next": "^15.1.4", "react-native": "0.72.15", - "react-native-adjust": "^5.0.2", + "react-native-adjust": "^5.0.3", "react-native-android-open-settings": "^1.3.0", "react-native-auth0": "^3.2.1", "react-native-camera": "^4.2.1", @@ -159,10 +159,10 @@ "react-native-picker-select": "^9.3.1", "react-native-platform-touchable": "^1.1.1", "react-native-qrcode-svg": "^6.3.12", - "react-native-quick-crypto": "^0.7.7", + "react-native-quick-crypto": "^0.7.9", "react-native-reanimated": "^3.15.1", "react-native-restart": "^0.0.27", - "react-native-safe-area-context": "^4.14.0", + "react-native-safe-area-context": "^4.14.1", "react-native-screens": "^4.3.0", "react-native-shake": "5.5.2", "react-native-share": "^11.1.0", diff --git a/src/app/saga.ts b/src/app/saga.ts index 927e26691ba..3b76a1f24fe 100644 --- a/src/app/saga.ts +++ b/src/app/saga.ts @@ -323,7 +323,11 @@ export function* handleDeepLink(action: OpenDeepLink) { } function* watchDeepLinks() { - yield* takeLatest(Actions.OPEN_DEEP_LINK, safely(handleDeepLink)) + // using takeEvery over takeLatest because openScreen deep links could be + // fired by multiple handlers (one with isSecureOrigin and one without), and + // if takeLatest kills the call to the handler with isSecureOrigin, the deep + // link won't work. + yield* takeEvery(Actions.OPEN_DEEP_LINK, safely(handleDeepLink)) } export function* handleOpenUrl(action: OpenUrlAction) { diff --git a/src/app/useDeepLinks.test.tsx b/src/app/useDeepLinks.test.tsx new file mode 100644 index 00000000000..99659260312 --- /dev/null +++ b/src/app/useDeepLinks.test.tsx @@ -0,0 +1,86 @@ +import { act, renderHook } from '@testing-library/react-native' +import CleverTap from 'clevertap-react-native' +import React from 'react' +import { Linking } from 'react-native' +import { Provider } from 'react-redux' +import { useDeepLinks } from 'src/app/useDeepLinks' +import { createMockStore } from 'test/utils' + +describe('useDeepLinks', () => { + let cleverTapListenerCallback: Function + let linkingListenerCallback: Function + + beforeEach(() => { + jest.clearAllMocks() + jest.mocked(CleverTap.addListener).mockImplementation((event, callback) => { + if (event === 'CleverTapPushNotificationClicked') { + cleverTapListenerCallback = callback + } + }) + jest.mocked(Linking.addEventListener).mockImplementation((event, callback) => { + if (event === 'url') { + linkingListenerCallback = callback + } + return { + remove: jest.fn(), + } as any + }) + }) + + it('should handle clevertap push notifications with deep links', async () => { + const store = createMockStore() + renderHook(() => useDeepLinks(), { + wrapper: (component) => ( + {component?.children ? component.children : component} + ), + }) + + await act(() => { + cleverTapListenerCallback({ wzrk_dl: 'some-link' }) + }) + + expect(store.getActions()).toEqual([ + { + deepLink: 'some-link', + isSecureOrigin: true, + type: 'APP/OPEN_DEEP_LINK', + }, + ]) + }) + + it('should not open deeplink if clevertap event does not have a deep link', async () => { + const store = createMockStore() + renderHook(() => useDeepLinks(), { + wrapper: (component) => ( + {component?.children ? component.children : component} + ), + }) + + await act(() => { + cleverTapListenerCallback({}) + }) + + expect(store.getActions()).toEqual([]) + }) + + it('should handle linking events with deep links', async () => { + const store = createMockStore() + renderHook(() => useDeepLinks(), { + wrapper: (component) => ( + {component?.children ? component.children : component} + ), + }) + + await act(() => { + linkingListenerCallback({ url: 'some-link' }) + }) + + expect(store.getActions()).toEqual([ + { + deepLink: 'some-link', + isSecureOrigin: false, + type: 'APP/OPEN_DEEP_LINK', + }, + ]) + }) +}) diff --git a/src/app/useDeepLinks.ts b/src/app/useDeepLinks.ts index f4629009b91..7efd41de7d3 100644 --- a/src/app/useDeepLinks.ts +++ b/src/app/useDeepLinks.ts @@ -2,7 +2,7 @@ import dynamicLinks from '@react-native-firebase/dynamic-links' import CleverTap from 'clevertap-react-native' import { useEffect, useState } from 'react' import { useAsync } from 'react-async-hook' -import { Linking, Platform } from 'react-native' +import { Linking } from 'react-native' import { deepLinkDeferred, openDeepLink } from 'src/app/actions' import { pendingDeepLinkSelector } from 'src/app/selectors' import { DYNAMIC_LINK_DOMAIN_URI_PREFIX, FIREBASE_ENABLED } from 'src/config' @@ -62,7 +62,7 @@ export const useDeepLinks = () => { } useAsync(async () => { - // Handles opening Clevertap deeplinks when app is closed / in background + // Handles opening Clevertap deeplinks when app is closed // @ts-expect-error the clevertap ts definition has url as an object, but it // is a string! CleverTap.getInitialUrl(async (err: any, url: string) => { @@ -94,17 +94,19 @@ export const useDeepLinks = () => { }, []) useEffect(() => { - // Handles opening Clevertap deeplinks when app is open + // Handles opening Clevertap deeplinks when app is open. CleverTap.addListener('CleverTapPushNotificationClicked', async (event: any) => { Logger.debug('useDeepLinks/useEffect', 'CleverTapPushNotificationClicked', event) - // Url location differs for iOS and Android - const url = Platform.OS === 'ios' ? event.customExtras['wzrk_dl'] : event['wzrk_dl'] + const url = event['wzrk_dl'] if (url) { Logger.debug('useDeepLinks/useEffect', 'CleverTapPushNotificationClicked, opening url', url) handleOpenURL({ url }, true) } }) + // Handles opening any deep links, this listener is also triggered when a + // its a clevertap push notification or when the app is closed, so the + // openDeepLink action could be dispatched multiple times in those cases. const linkingEventListener = Linking.addEventListener('url', (event) => { Logger.debug('useDeepLinks/useEffect', 'Linking url event', event) handleOpenURL(event) diff --git a/src/earn/poolInfoScreen/BeforeDepositBottomSheet.test.tsx b/src/earn/poolInfoScreen/BeforeDepositBottomSheet.test.tsx new file mode 100644 index 00000000000..c2cb12a8692 --- /dev/null +++ b/src/earn/poolInfoScreen/BeforeDepositBottomSheet.test.tsx @@ -0,0 +1,313 @@ +import { fireEvent, render } from '@testing-library/react-native' +import BigNumber from 'bignumber.js' +import React from 'react' +import { Provider } from 'react-redux' +import BeforeDepositBottomSheet from 'src/earn/poolInfoScreen/BeforeDepositBottomSheet' +import { CICOFlow } from 'src/fiatExchanges/types' +import { navigate } from 'src/navigator/NavigationService' +import { Screens } from 'src/navigator/Screens' +import { createMockStore } from 'test/utils' +import { mockEarnPositions, mockTokenBalances } from 'test/values' + +const mockPoolTokenId = mockEarnPositions[0].dataProps.depositTokenId +const mockToken = { + ...mockTokenBalances[mockPoolTokenId], + balance: new BigNumber(1), + priceUsd: new BigNumber(1), + lastKnownPriceUsd: new BigNumber(1), +} + +describe('BeforeDepositBottomSheet', () => { + it('show bottom sheet correctly when hasDepositToken is true, no other tokens', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Deposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/AddMore')).toBeTruthy() + }) + it('show bottom sheet correctly when hasDepositToken is true, token(s) on same chain', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Deposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/AddMore')).toBeTruthy() + }) + it('show bottom sheet correctly when hasDepositToken is true, token(s) on differnet chain', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Deposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/Swap')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/AddMore')).toBeTruthy() + }) + it('show bottom sheet correctly when hasDepositToken is true, token(s) on same and different chains', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Deposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/CrossChainSwap')).toBeTruthy() + }) + it('show bottom sheet correctly when hasDepositToken is false, can same and cross chain swap', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/CrossChainSwap')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/Add')).toBeTruthy() + }) + + it('show bottom sheet correctly when hasDepositToken is false, can cross chain swap', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Swap')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/Add')).toBeTruthy() + }) + + it('show bottom sheet correctly when hasDepositToken is false, no tokens', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Add')).toBeTruthy() + expect(getByTestId('Earn/ActionCard/Transfer')).toBeTruthy() + }) + + it('navigates correctly when deposit action item is tapped', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Deposit')).toBeTruthy() + fireEvent.press(getByTestId('Earn/ActionCard/Deposit')) + expect(navigate).toHaveBeenCalledWith(Screens.EarnEnterAmount, { + pool: mockEarnPositions[0], + }) + }) + + it('navigates correctly when swap and deposit action item is tapped', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy() + fireEvent.press(getByTestId('Earn/ActionCard/SwapAndDeposit')) + expect(navigate).toHaveBeenCalledWith(Screens.EarnEnterAmount, { + pool: mockEarnPositions[0], + mode: 'swap-deposit', + }) + }) + + it('navigates correctly when cross chain swap action item is tapped', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/CrossChainSwap')).toBeTruthy() + fireEvent.press(getByTestId('Earn/ActionCard/CrossChainSwap')) + expect(navigate).toHaveBeenCalledWith(Screens.SwapScreenWithBack, { + toTokenId: mockEarnPositions[0].dataProps.depositTokenId, + }) + }) + + it('navigates correctly when add more action item is tapped', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/AddMore')).toBeTruthy() + fireEvent.press(getByTestId('Earn/ActionCard/AddMore')) + expect(navigate).toHaveBeenCalledWith(Screens.FiatExchangeAmount, { + tokenId: mockEarnPositions[0].dataProps.depositTokenId, + flow: CICOFlow.CashIn, + tokenSymbol: 'USDC', + }) + }) + + it('navigates correctly when add action item is tapped', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Add')).toBeTruthy() + fireEvent.press(getByTestId('Earn/ActionCard/Add')) + expect(navigate).toHaveBeenCalledWith(Screens.FiatExchangeAmount, { + tokenId: mockEarnPositions[0].dataProps.depositTokenId, + flow: CICOFlow.CashIn, + tokenSymbol: 'USDC', + }) + }) + + it('navigates correctly when swap action item is tapped', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Swap')).toBeTruthy() + fireEvent.press(getByTestId('Earn/ActionCard/Swap')) + expect(navigate).toHaveBeenCalledWith(Screens.SwapScreenWithBack, { + toTokenId: mockEarnPositions[0].dataProps.depositTokenId, + }) + }) + + it('navigates correctly when transfer action item is tapped', () => { + const { getByTestId } = render( + + + + ) + expect(getByTestId('Earn/ActionCard/Transfer')).toBeTruthy() + fireEvent.press(getByTestId('Earn/ActionCard/Transfer')) + expect(navigate).toHaveBeenCalledWith(Screens.ExchangeQR, { + flow: CICOFlow.CashIn, + exchanges: [], + }) + }) +}) diff --git a/src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx b/src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx index f163e75c867..42cabb5d6f1 100644 --- a/src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx +++ b/src/earn/poolInfoScreen/BeforeDepositBottomSheet.tsx @@ -9,6 +9,7 @@ import { ActionCard } from 'src/earn/ActionCard' import { BeforeDepositAction } from 'src/earn/types' import { ExternalExchangeProvider } from 'src/fiatExchanges/ExternalExchanges' import { CICOFlow } from 'src/fiatExchanges/types' +import EarnCoins from 'src/icons/EarnCoins' import QuickActionsAdd from 'src/icons/quick-actions/Add' import QuickActionsSend from 'src/icons/quick-actions/Send' import SwapAndDeposit from 'src/icons/SwapAndDeposit' @@ -59,6 +60,42 @@ function AddAction({ return } +function AddMoreAction({ + token, + forwardedRef, + analyticsProps, +}: { + token: TokenBalance + forwardedRef: React.RefObject + analyticsProps: EarnCommonProperties & TokenProperties +}) { + const { t } = useTranslation() + + const action: BeforeDepositAction = { + name: 'AddMore', + title: t('earnFlow.beforeDepositBottomSheet.action.addMore', { tokenSymbol: token.symbol }), + details: t('earnFlow.beforeDepositBottomSheet.action.addDescription', { + tokenSymbol: token.symbol, + tokenNetwork: NETWORK_NAMES[token.networkId], + }), + iconComponent: QuickActionsAdd, + onPress: () => { + AppAnalytics.track(EarnEvents.earn_before_deposit_action_press, { + action: 'AddMore', + ...analyticsProps, + }) + + navigate(Screens.FiatExchangeAmount, { + tokenId: token.tokenId, + flow: CICOFlow.CashIn, + tokenSymbol: token.symbol, + }) + forwardedRef.current?.close() + }, + } + return +} + function TransferAction({ token, exchanges, @@ -190,10 +227,45 @@ function SwapAndDepositAction({ return } +function DepositAction({ + token, + pool, + forwardedRef, + analyticsProps, +}: { + token: TokenBalance + pool: EarnPosition + forwardedRef: React.RefObject + analyticsProps: EarnCommonProperties & TokenProperties +}) { + const { t } = useTranslation() + + const action: BeforeDepositAction = { + name: 'Deposit', + title: t('earnFlow.beforeDepositBottomSheet.action.deposit', { tokenSymbol: token.symbol }), + details: t('earnFlow.beforeDepositBottomSheet.action.depositDescription', { + amount: token.balance, + tokenSymbol: token.symbol, + }), + iconComponent: EarnCoins, + onPress: () => { + AppAnalytics.track(EarnEvents.earn_before_deposit_action_press, { + action: 'Deposit', + ...analyticsProps, + }) + + navigate(Screens.EarnEnterAmount, { pool }) + forwardedRef.current?.close() + }, + } + return +} + export default function BeforeDepositBottomSheet({ forwardedRef, token, pool, + hasDepositToken, hasTokensOnSameNetwork, hasTokensOnOtherNetworks, canAdd, @@ -202,6 +274,7 @@ export default function BeforeDepositBottomSheet({ forwardedRef: RefObject token: TokenBalance pool: EarnPosition + hasDepositToken: boolean hasTokensOnSameNetwork: boolean hasTokensOnOtherNetworks: boolean canAdd: boolean @@ -212,12 +285,10 @@ export default function BeforeDepositBottomSheet({ const { availableShortcutIds } = pool const canSwapDeposit = availableShortcutIds.includes('swap-deposit') && hasTokensOnSameNetwork - const title = canSwapDeposit - ? t('earnFlow.beforeDepositBottomSheet.youNeedTitle', { - tokenSymbol: token.symbol, - tokenNetwork: NETWORK_NAMES[token.networkId], - }) - : t('earnFlow.beforeDepositBottomSheet.beforeYouCanDepositTitle') + const title = + canSwapDeposit || hasDepositToken + ? t('earnFlow.beforeDepositBottomSheet.depositTitle') + : t('earnFlow.beforeDepositBottomSheet.beforeYouCanDepositTitle') const analyticsProps = { ...getTokenAnalyticsProps(token), @@ -226,48 +297,81 @@ export default function BeforeDepositBottomSheet({ depositTokenId: pool.dataProps.depositTokenId, } + const showCrossChainSwap = canSwapDeposit && hasTokensOnOtherNetworks + const showSwap = !canSwapDeposit && (hasTokensOnSameNetwork || hasTokensOnOtherNetworks) + const showAdd = canAdd && !hasDepositToken + // Show AddMore if the token is available for cash-in, the user has the deposit token, + // and does not have both tokens on the same and different networks (in which case deposit and both swap and cross-chain swap will show instead) + const showAddMore = + canAdd && hasDepositToken && !(hasTokensOnSameNetwork && hasTokensOnOtherNetworks) + // Show Transfer if the user does not have the deposit token and does not have any tokens available for swapping + // OR if the token is not a cash-in token and the user does not have the deposit token and both tokens on the same and different networks + // (in which case deposit and both swap and cross-chain swap will show instead) + const showTransfer = + (!hasDepositToken && !hasTokensOnSameNetwork && !hasTokensOnOtherNetworks) || + (!canAdd && !(hasDepositToken && hasTokensOnSameNetwork && hasTokensOnOtherNetworks)) + return ( + {hasDepositToken && ( + + )} {canSwapDeposit && ( - <> - + + )} + {(canSwapDeposit || hasDepositToken) && + (showCrossChainSwap || showSwap || showAdd || showAddMore || showTransfer) && ( {t('earnFlow.beforeDepositBottomSheet.crossChainAlternativeDescription', { tokenNetwork: NETWORK_NAMES[token.networkId], })} - {hasTokensOnOtherNetworks && ( - - )} - + )} + {showCrossChainSwap && ( + )} - {!canSwapDeposit && (hasTokensOnSameNetwork || hasTokensOnOtherNetworks) && ( + {showSwap && ( )} - {canAdd && ( + {showAdd && ( )} - {!canSwapDeposit && ( + {showAddMore && ( + + )} + {showTransfer && ( { }) }) - it('navigate to EarnEnterAmount when Deposit button is tapped and depositTokenId has a balance', () => { - const { getByText } = render( + it('Show bottom sheet when Deposit button is tapped and depositTokenId has a balance', () => { + const { getByText, getByTestId } = render( @@ -406,221 +398,13 @@ describe('EarnPoolInfoScreen', () => { hasTokensOnSameNetwork: false, hasTokensOnOtherNetworks: false, }) - expect(navigate).toHaveBeenCalledWith(Screens.EarnEnterAmount, { - pool: mockEarnPositions[0], - }) - }) - - it('show bottom sheet correctly when Deposit button is tapped and depositTokenId does not have balance, can same and cross chain swap', () => { - const { getByText, getByTestId } = render( - - { - return ( - - ) - }} - /> - - ) - fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) - expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_tap_deposit, { - providerId: 'aave', - poolId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', - networkId: 'arbitrum-sepolia', - depositTokenId: mockEarnPositions[0].dataProps.depositTokenId, - hasDepositToken: false, - hasTokensOnSameNetwork: true, - hasTokensOnOtherNetworks: true, - }) - expect(getByTestId('Earn/BeforeDepositBottomSheet')).toBeVisible() - expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy() - expect(getByTestId('Earn/ActionCard/CrossChainSwap')).toBeTruthy() - expect(getByTestId('Earn/ActionCard/Add')).toBeTruthy() - }) - - it('navigates correctly when swap and deposit action item is tapped', () => { - const { getByText, getByTestId } = render( - - { - return ( - - ) - }} - /> - - ) - fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) - expect(getByTestId('Earn/ActionCard/SwapAndDeposit')).toBeTruthy() - fireEvent.press(getByTestId('Earn/ActionCard/SwapAndDeposit')) - expect(navigate).toHaveBeenCalledWith(Screens.EarnEnterAmount, { - pool: mockEarnPositions[0], - mode: 'swap-deposit', - }) - expect(AppAnalytics.track).toHaveBeenCalledTimes(2) - }) - - it('navigates correctly when cross chain swap action item is tapped', () => { - const { getByText, getByTestId } = render( - - { - return ( - - ) - }} - /> - - ) - fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) - expect(getByTestId('Earn/ActionCard/CrossChainSwap')).toBeTruthy() - fireEvent.press(getByTestId('Earn/ActionCard/CrossChainSwap')) - expect(navigate).toHaveBeenCalledWith(Screens.SwapScreenWithBack, { - toTokenId: mockEarnPositions[0].dataProps.depositTokenId, - }) - expect(AppAnalytics.track).toHaveBeenCalledTimes(2) - }) - - it('navigates correctly when add action item is tapped', () => { - const { getByText, getByTestId } = render( - - { - return ( - - ) - }} - /> - - ) - fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) - expect(getByTestId('Earn/ActionCard/Add')).toBeTruthy() - fireEvent.press(getByTestId('Earn/ActionCard/Add')) - expect(navigate).toHaveBeenCalledWith(Screens.FiatExchangeAmount, { - tokenId: mockEarnPositions[0].dataProps.depositTokenId, - flow: CICOFlow.CashIn, - tokenSymbol: 'USDC', - }) - expect(AppAnalytics.track).toHaveBeenCalledTimes(2) - }) - - it('show bottom sheet correctly when Deposit button is tapped and depositTokenId does not have balance, can cross chain swap', () => { - const { getByText, getByTestId } = render( - - { - return ( - - ) - }} - /> - - ) - fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) - expect(AppAnalytics.track).toHaveBeenCalledWith(EarnEvents.earn_pool_info_tap_deposit, { - providerId: 'aave', - poolId: 'arbitrum-sepolia:0x460b97bd498e1157530aeb3086301d5225b91216', - networkId: 'arbitrum-sepolia', - depositTokenId: mockEarnPositions[0].dataProps.depositTokenId, - hasDepositToken: false, - hasTokensOnSameNetwork: false, - hasTokensOnOtherNetworks: true, - }) expect(getByTestId('Earn/BeforeDepositBottomSheet')).toBeVisible() - expect(getByTestId('Earn/ActionCard/Swap')).toBeTruthy() - expect(getByTestId('Earn/ActionCard/Add')).toBeTruthy() - expect(getByTestId('Earn/ActionCard/Transfer')).toBeTruthy() }) - it('navigates correctly when swap action item is tapped', () => { - jest - .mocked(getFeatureGate) - .mockImplementation((gate) => gate === StatsigFeatureGates.ALLOW_CROSS_CHAIN_SWAPS) - const { getByText, getByTestId } = render( - - { - return ( - - ) - }} - /> - - ) - fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) - expect(getByTestId('Earn/ActionCard/Swap')).toBeTruthy() - fireEvent.press(getByTestId('Earn/ActionCard/Swap')) - expect(navigate).toHaveBeenCalledWith(Screens.SwapScreenWithBack, { - toTokenId: mockEarnPositions[0].dataProps.depositTokenId, - }) - expect(AppAnalytics.track).toHaveBeenCalledTimes(2) - }) - - it('navigates correctly when transfer action item is tapped', () => { - const { getByText, getByTestId } = render( - - { - return ( - - ) - }} - /> - - ) - fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) - expect(getByTestId('Earn/ActionCard/Transfer')).toBeTruthy() - fireEvent.press(getByTestId('Earn/ActionCard/Transfer')) - expect(navigate).toHaveBeenCalledWith(Screens.ExchangeQR, { - flow: CICOFlow.CashIn, - exchanges: [], - }) - expect(AppAnalytics.track).toHaveBeenCalledTimes(2) - }) - - it('show bottom sheet correctly when Deposit button is tapped and depositTokenId does not have balance, no tokens', () => { + it('Show bottom sheet when Deposit button is tapped and depositTokenId does not have a balance', () => { const { getByText, getByTestId } = render( - { - return ( - - ) - }} - /> + ) fireEvent.press(getByText('earnFlow.poolInfoScreen.deposit')) @@ -634,8 +418,6 @@ describe('EarnPoolInfoScreen', () => { hasTokensOnOtherNetworks: false, }) expect(getByTestId('Earn/BeforeDepositBottomSheet')).toBeVisible() - expect(getByTestId('Earn/ActionCard/Add')).toBeTruthy() - expect(getByTestId('Earn/ActionCard/Transfer')).toBeTruthy() }) it('navigate to EarnConfirmationScreen when Withdraw button is tapped, no rewards and cannot partial withdraw', () => { diff --git a/src/earn/poolInfoScreen/EarnPoolInfoScreen.tsx b/src/earn/poolInfoScreen/EarnPoolInfoScreen.tsx index 1277f5e92dd..5c438d66ff8 100644 --- a/src/earn/poolInfoScreen/EarnPoolInfoScreen.tsx +++ b/src/earn/poolInfoScreen/EarnPoolInfoScreen.tsx @@ -241,11 +241,7 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) { hasTokensOnSameNetwork, hasTokensOnOtherNetworks, }) - if (hasDepositToken) { - navigate(Screens.EarnEnterAmount, { pool }) - } else { - beforeDepositBottomSheetRef.current?.snapToIndex(0) - } + beforeDepositBottomSheetRef.current?.snapToIndex(0) } const beforeDepositBottomSheetRef = useRef(null) @@ -427,6 +423,7 @@ export default function EarnPoolInfoScreen({ route, navigation }: Props) { forwardedRef={beforeDepositBottomSheetRef} token={depositToken} pool={pool} + hasDepositToken={hasDepositToken} hasTokensOnSameNetwork={hasTokensOnSameNetwork} hasTokensOnOtherNetworks={hasTokensOnOtherNetworks} canAdd={canCashIn} diff --git a/src/earn/types.ts b/src/earn/types.ts index 7f81cba0ce4..56c4e55d254 100644 --- a/src/earn/types.ts +++ b/src/earn/types.ts @@ -43,10 +43,12 @@ export interface PoolInfo { export type BeforeDepositActionName = | 'Add' + | 'AddMore' | 'Transfer' | 'SwapAndDeposit' | 'CrossChainSwap' | 'Swap' + | 'Deposit' export interface BeforeDepositAction { name: BeforeDepositActionName diff --git a/yarn.lock b/yarn.lock index bcc781e3cbc..9967db14f1c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1588,27 +1588,27 @@ resolved "https://registry.yarnpkg.com/@faker-js/faker/-/faker-5.5.3.tgz#18e3af6b8eae7984072bbeb0c0858474d7c4cefe" integrity sha512-R11tGE6yIFwqpaIqcfkcg7AICXzFg14+5h5v0TfF/9+RMDL6jhzCy/pxHVOfbALGdtVYdt6JdR21tuxEgl34dw== -"@fiatconnect/fiatconnect-sdk@^0.5.62": - version "0.5.62" - resolved "https://registry.yarnpkg.com/@fiatconnect/fiatconnect-sdk/-/fiatconnect-sdk-0.5.62.tgz#09b062be9b44aa340afdbe857b61c904681becbb" - integrity sha512-aWy1RHAZf4WJ1weAsCdk/twmSmKnriyaTyEsfCGERPr9H8xQ6KzrC8QbJccCzGylMO/VcFlT1KYg3XS+09nBSA== +"@fiatconnect/fiatconnect-sdk@^0.5.66": + version "0.5.66" + resolved "https://registry.yarnpkg.com/@fiatconnect/fiatconnect-sdk/-/fiatconnect-sdk-0.5.66.tgz#8bf79df0c0c6be0d71a39905f6e6d7d7d814d29d" + integrity sha512-bS7Z1Cvl1u02Br8ZGTkDvnJllhyWBqbt7aXpUS64KfgMOZXpMhKbwmgfDHE2zXdYkbQZzG4/hdR6lpQFM4KvYw== dependencies: "@badrap/result" "^0.2.13" - "@fiatconnect/fiatconnect-types" "^13.3.8" + "@fiatconnect/fiatconnect-types" "^13.3.10" cross-fetch "^4.0.0" ethers "^6.13.4" fetch-cookie "^3.0.1" siwe "^2.3.2" tough-cookie "^5.0.0" tslib "^2.8.1" - zod "^3.23.8" + zod "^3.24.1" -"@fiatconnect/fiatconnect-types@^13.3.8": - version "13.3.8" - resolved "https://registry.yarnpkg.com/@fiatconnect/fiatconnect-types/-/fiatconnect-types-13.3.8.tgz#9f358a39b79ec9c640305e5f5a74fc6cef0bff38" - integrity sha512-SL3CMrrikxI1rnNjfisQ/M27XgH9Tz4PuNOILIk9LmSoYvWAzVNY+i4su3IiBnT/DrAHvibcYc7JFWV9LQlAOw== +"@fiatconnect/fiatconnect-types@^13.3.10": + version "13.3.10" + resolved "https://registry.yarnpkg.com/@fiatconnect/fiatconnect-types/-/fiatconnect-types-13.3.10.tgz#ce689c97b98fae843b4f356add5ac62958b3ca65" + integrity sha512-ca/MtaQGAcKPPedbl0WN+97nexlFL2yi+QrTmMLgCEpCJz12ceixEZJlDUZfvBvHIQAsleba9HUSZFcXIn38FA== dependencies: - zod "^3.23.8" + zod "^3.24.1" "@flatten-js/interval-tree@^1.1.2": version "1.1.3" @@ -8435,10 +8435,10 @@ globby@^14.0.0: slash "^5.1.0" unicorn-magic "^0.1.0" -google-libphonenumber@^3.2.39: - version "3.2.39" - resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.39.tgz#915fca5e75f6fb11b2cb7d5fe47eeb7ff65fe9f0" - integrity sha512-dpCbkY6ZxHXIHEFDwSir/gPBWkn22e2EixBv47guVs/NE8+qd35f1yu+fxQ8awRnHEXC60uhcPM9mbqmrD6nmw== +google-libphonenumber@^3.2.40: + version "3.2.40" + resolved "https://registry.yarnpkg.com/google-libphonenumber/-/google-libphonenumber-3.2.40.tgz#ed57d0db67a8745e8e6db7983dda7f94717fb4c9" + integrity sha512-bzGxX/vfggcV80LVcibki+JvR91x6zHpBpovDXSfmZUGn6uLzhbYXsWll2a80EG6qTmvf8lt7KZZ/pkMml8ckw== gopd@^1.0.1: version "1.0.1" @@ -8894,10 +8894,10 @@ invariant@2.2.4, invariant@^2.2.2, invariant@^2.2.4: dependencies: loose-envify "^1.0.0" -io-ts@2.2.21: - version "2.2.21" - resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.21.tgz#4ef754176f7082a1099d04c7d5c4ea53267c530a" - integrity sha512-zz2Z69v9ZIC3mMLYWIeoUcwWD6f+O7yP92FMVVaXEOSZH1jnVBmET/urd/uoarD1WGBY4rCj8TAyMPzsGNzMFQ== +io-ts@2.2.22: + version "2.2.22" + resolved "https://registry.yarnpkg.com/io-ts/-/io-ts-2.2.22.tgz#5ab0d3636fe8494a275f0266461ab019da4b8d0b" + integrity sha512-FHCCztTkHoV9mdBsHpocLpdTAfh956ZQcIkWQxxS0U5HT53vtrcuYdQneEJKH6xILaLNzXVl2Cvwtoy8XNN0AA== ip-regex@^4.0.0: version "4.3.0" @@ -12512,10 +12512,10 @@ react-freeze@^1.0.0: resolved "https://registry.yarnpkg.com/react-freeze/-/react-freeze-1.0.0.tgz#b21c65fe1783743007c8c9a2952b1c8879a77354" integrity sha512-yQaiOqDmoKqks56LN9MTgY06O0qQHgV4FUrikH357DydArSZHQhl0BJFqGKIZoTqi8JizF9Dxhuk1FIZD6qCaw== -react-i18next@^15.1.3: - version "15.1.3" - resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.1.3.tgz#172c3905038ea4f90699a19949a0084b5641c94f" - integrity sha512-J11oA30FbM3NZegUZjn8ySK903z6PLBz/ZuBYyT1JMR0QPrW6PFXvl1WoUhortdGi9dM0m48/zJQlPskVZXgVw== +react-i18next@^15.1.4: + version "15.2.0" + resolved "https://registry.yarnpkg.com/react-i18next/-/react-i18next-15.2.0.tgz#6b51650e1e93eb4d235a4d533fcf61b3bbf4ea10" + integrity sha512-iJNc8111EaDtVTVMKigvBtPHyrJV+KblWG73cUxqp+WmJCcwkzhWNFXmkAD5pwP2Z4woeDj/oXDdbjDsb3Gutg== dependencies: "@babel/runtime" "^7.25.0" html-parse-stringify "^3.0.1" @@ -12535,10 +12535,10 @@ react-is@^18.0.0, react-is@^18.2.0: resolved "https://registry.yarnpkg.com/react-is/-/react-is-18.2.0.tgz#199431eeaaa2e09f86427efbb4f1473edb47609b" integrity sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w== -react-native-adjust@^5.0.2: - version "5.0.2" - resolved "https://registry.yarnpkg.com/react-native-adjust/-/react-native-adjust-5.0.2.tgz#3739518b7ee3c212de63ef12195c50c313a0cc80" - integrity sha512-X1kx1/9DIMxxfUHiHbjm/W238OiYs5KrbJujhoXde8TU4mDVaCZCKpJ8BFJWOHIdiB/vbHrOtwDMN6KlyxZang== +react-native-adjust@^5.0.3: + version "5.0.3" + resolved "https://registry.yarnpkg.com/react-native-adjust/-/react-native-adjust-5.0.3.tgz#1f60ccb198d52d251619ec01543475b9635f5367" + integrity sha512-P+/Sx961Plc9AtlRWL4TbP+Gi4Hwsyw7COnehlyl4f/cktAdtydrl7Q8mBtC41JQvLoXam3YZaNXNsmcXGkwoA== react-native-android-open-settings@^1.3.0: version "1.3.0" @@ -12727,10 +12727,10 @@ react-native-quick-base64@^2.0.5: dependencies: base64-js "^1.5.1" -react-native-quick-crypto@^0.7.7: - version "0.7.7" - resolved "https://registry.yarnpkg.com/react-native-quick-crypto/-/react-native-quick-crypto-0.7.7.tgz#2126beff48da8f5c71210fc89b93a15ce8f5f126" - integrity sha512-+P7MWxo0KSCCyO6rwcW654l+GYV5tVkI7cusHSL/iCahnWCisuyBLzJENzdv6MqDM9pb59QJWbrLfOzpUHZpNw== +react-native-quick-crypto@^0.7.9: + version "0.7.10" + resolved "https://registry.yarnpkg.com/react-native-quick-crypto/-/react-native-quick-crypto-0.7.10.tgz#b65359348b4c1f2677c52ef54ab568f81627a4cf" + integrity sha512-ziupKopD1o58v+ywL8aTvJMXBpGf89xLQc3JKG5CRSdEUfTUu5e4ru43KIXrG6uleCX8pcgD6e6RsMqrdEy0zw== dependencies: "@craftzdog/react-native-buffer" "^6.0.5" events "^3.3.0" @@ -12760,7 +12760,7 @@ react-native-restart@^0.0.27: resolved "https://registry.yarnpkg.com/react-native-restart/-/react-native-restart-0.0.27.tgz#43aa8210312c9dfa5ec7bd4b2f35238ad7972b19" integrity sha512-8KScVICrXwcTSJ1rjWkqVTHyEKQIttm5AIMGSK1QG1+RS5owYlE4z/1DykOTdWfVl9l16FIk0w9Xzk9ZO6jxlA== -react-native-safe-area-context@^4.14.0: +react-native-safe-area-context@^4.14.1: version "4.14.1" resolved "https://registry.yarnpkg.com/react-native-safe-area-context/-/react-native-safe-area-context-4.14.1.tgz#980c3c9db02a2314fa8ceb07f614728802320367" integrity sha512-+tUhT5WBl8nh5+P+chYhAjR470iCByf9z5EYdCEbPaAK3Yfzw+o8VRPnUgmPAKlSccOgQBxx3NOl/Wzckn9ujg== @@ -16022,7 +16022,7 @@ zod@3.22.4: resolved "https://registry.yarnpkg.com/zod/-/zod-3.22.4.tgz#f31c3a9386f61b1f228af56faa9255e845cf3fff" integrity sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg== -zod@^3.21.4, zod@^3.23.8: - version "3.23.8" - resolved "https://registry.yarnpkg.com/zod/-/zod-3.23.8.tgz#e37b957b5d52079769fb8097099b592f0ef4067d" - integrity sha512-XBx9AXhXktjUqnepgTiE5flcKIYWi/rme0Eaj+5Y0lftuGBq+jyRu/md4WnuxqgP1ubdpNCsYEYPxrzVHD8d6g== +zod@^3.21.4, zod@^3.24.1: + version "3.24.1" + resolved "https://registry.yarnpkg.com/zod/-/zod-3.24.1.tgz#27445c912738c8ad1e9de1bea0359fa44d9d35ee" + integrity sha512-muH7gBL9sI1nciMZV67X5fTKKBLtwpZ5VBp1vsOQzj1MhrBZ4wlVCm3gedKZWLp0Oyel8sIGfeiz54Su+OVT+A==