From 754fa6646b0e142e237104f97f6a16c8998a49ce Mon Sep 17 00:00:00 2001 From: Gabriel Date: Wed, 13 Sep 2023 17:48:15 -0300 Subject: [PATCH] [FEAT] scanner incomingData unit tests --- package.json | 4 +- src/lib/Mixpanel/index.ts | 5 +- src/navigation/scan/screens/Scan.tsx | 4 +- .../wallet/screens/GlobalSelect.tsx | 14 +- src/store/index.ts | 8 +- src/store/scan/scan.effects.ts | 50 +- src/store/scan/scan.spec.ts | 1177 +++++++++++++++++ src/store/wallet/effects/paypro/paypro.ts | 1 + test/setup.js | 12 + 9 files changed, 1242 insertions(+), 33 deletions(-) create mode 100644 src/store/scan/scan.spec.ts diff --git a/package.json b/package.json index cc04d745b..2238f0ac2 100644 --- a/package.json +++ b/package.json @@ -9,8 +9,8 @@ "ios:device": "export NODE_OPTIONS=--openssl-legacy-provider && react-native run-ios --device", "ios:device:release": "export NODE_OPTIONS=--openssl-legacy-provider && react-native run-ios --configuration Release --device", "start": "export NODE_OPTIONS=--openssl-legacy-provider && react-native start", - "test:coverage": "jest --coverage --testMatch='**/*.spec.{js,tsx}' --config='jest.config.js'", - "test:unit": "jest --watch --testMatch='**/*.spec.{js,tsx}' --config='jest.config.js'", + "test:coverage": "jest --coverage --testMatch='**/*.spec.{js,tsx,ts}' --config='jest.config.js'", + "test:unit": "jest --watch --testMatch='**/*.spec.{js,tsx,ts}' --config='jest.config.js'", "test:cache": "jest --cache", "prebuild:android:debug": "yarn set:dev", "build:android:debug": "export NODE_OPTIONS=--openssl-legacy-provider && ./scripts/android-debug.sh", diff --git a/src/lib/Mixpanel/index.ts b/src/lib/Mixpanel/index.ts index 91c339dd5..47faec4a8 100644 --- a/src/lib/Mixpanel/index.ts +++ b/src/lib/Mixpanel/index.ts @@ -2,9 +2,8 @@ import {MIXPANEL_PROJECT_TOKEN} from '@env'; import {Mixpanel, MixpanelProperties} from 'mixpanel-react-native'; export const MixpanelWrapper = (() => { - const _client = MIXPANEL_PROJECT_TOKEN - ? new Mixpanel(MIXPANEL_PROJECT_TOKEN, true) - : null; + const token = MIXPANEL_PROJECT_TOKEN; + const _client = token ? new Mixpanel(token, true) : null; const guard = async (cb: (mp: Mixpanel) => T) => { if (_client) { diff --git a/src/navigation/scan/screens/Scan.tsx b/src/navigation/scan/screens/Scan.tsx index 33d569ff0..3d3256f41 100644 --- a/src/navigation/scan/screens/Scan.tsx +++ b/src/navigation/scan/screens/Scan.tsx @@ -2,7 +2,6 @@ import React from 'react'; import {RNCamera} from 'react-native-camera'; import styled from 'styled-components/native'; import ScanGuideSvg from '../../../../assets/img/qr-scan-guides.svg'; -import {useDispatch} from 'react-redux'; import {incomingData} from '../../../store/scan/scan.effects'; import debounce from 'lodash.debounce'; import {useRoute} from '@react-navigation/native'; @@ -12,6 +11,7 @@ import {navigationRef} from '../../../Root'; import {AppActions} from '../../../store/app'; import {CustomErrorMessage} from '../../wallet/components/ErrorMessages'; import {useTranslation} from 'react-i18next'; +import {useAppDispatch} from '../../../utils/hooks'; const ScanContainer = styled.SafeAreaView` flex: 1; @@ -31,7 +31,7 @@ interface Props { const Scan = () => { const {t} = useTranslation(); - const dispatch = useDispatch(); + const dispatch = useAppDispatch(); const route = useRoute>(); const {onScanComplete} = route.params || {}; diff --git a/src/navigation/wallet/screens/GlobalSelect.tsx b/src/navigation/wallet/screens/GlobalSelect.tsx index 14e725e67..72ddb570b 100644 --- a/src/navigation/wallet/screens/GlobalSelect.tsx +++ b/src/navigation/wallet/screens/GlobalSelect.tsx @@ -7,7 +7,11 @@ import React, { } from 'react'; import styled from 'styled-components/native'; import {useAppDispatch, useAppSelector} from '../../../utils/hooks'; -import {SUPPORTED_COINS, SUPPORTED_TOKENS} from '../../../constants/currencies'; +import { + BitpaySupportedEvmCoins, + SUPPORTED_COINS, + SUPPORTED_TOKENS, +} from '../../../constants/currencies'; import {Wallet} from '../../../store/wallet/wallet.models'; import { convertToFiat, @@ -144,7 +148,7 @@ export type GlobalSelectParamList = { sendMax?: boolean | undefined; message?: string; feePerKb?: number; - showERC20Tokens?: boolean; + showEVMWalletsAndTokens?: boolean; }; }; amount?: number; @@ -262,8 +266,10 @@ const GlobalSelect: React.FC = ({ if (recipient.currency && recipient.chain) { wallets = wallets.filter( wallet => - wallet.currencyAbbreviation === recipient?.currency && - wallet.chain === recipient?.chain, + (wallet.currencyAbbreviation === recipient?.currency && + wallet.chain === recipient?.chain) || + (recipient?.opts?.showEVMWalletsAndTokens && + BitpaySupportedEvmCoins[wallet.currencyAbbreviation]), ); } if (recipient?.network) { diff --git a/src/store/index.ts b/src/store/index.ts index 127b1df70..dd67061c0 100644 --- a/src/store/index.ts +++ b/src/store/index.ts @@ -1,6 +1,8 @@ import {DISABLE_DEVELOPMENT_LOGGING} from '@env'; import { Action, + AnyAction, + Store, applyMiddleware, combineReducers, legacy_createStore as createStore, @@ -58,6 +60,7 @@ import { import {Storage} from 'redux-persist'; import {MMKV} from 'react-native-mmkv'; +import {AppDispatch} from '../utils/hooks'; export const storage = new MMKV(); @@ -241,5 +244,8 @@ export function configureTestStore(initialState: any) { trace: true, traceLimit: 25, })(applyMiddleware(...middlewares)); - return createStore(rootReducer, initialState, middlewareEnhancers); + const store = createStore(rootReducer, initialState, middlewareEnhancers); + return store as Store & { + dispatch: AppDispatch; + }; } diff --git a/src/store/scan/scan.effects.ts b/src/store/scan/scan.effects.ts index 1d011c162..f72bbcf42 100644 --- a/src/store/scan/scan.effects.ts +++ b/src/store/scan/scan.effects.ts @@ -185,7 +185,7 @@ export const incomingData = dispatch(handleBitPayUri(data, opts?.wallet)); // Import Private Key } else if (IsValidImportPrivateKey(data)) { - goToImport(data); + dispatch(goToImport(data)); // Join multisig wallet } else if (IsValidJoinCode(data)) { dispatch(goToJoinWallet(data)); @@ -229,7 +229,7 @@ const goToPayPro = const invoiceId = data.split('/i/')[1].split('?')[0]; const payProUrl = GetPayProUrl(data); const {host} = new URL(payProUrl); - + dispatch(LogActions.info('[scan] Incoming-data: Payment Protocol request')); try { dispatch(startOnGoingProcessModal('FETCHING_PAYMENT_INFO')); const payProOptions = await dispatch(GetPayProOptions(payProUrl)); @@ -266,8 +266,6 @@ const goToPayPro = }); } catch (e: any) { dispatch(dismissOnGoingProcessModal()); - await sleep(800); - dispatch( showBottomNotificationModal({ type: 'warning', @@ -476,9 +474,10 @@ const goToConfirm = ...recipient, ...{ opts: { - showERC20Tokens: - !!BitpaySupportedEvmCoins[recipient.currency.toLowerCase()], // no wallet selected - if ETH address show token wallets in next view + showEVMWalletsAndTokens: + !!BitpaySupportedEvmCoins[recipient.currency.toLowerCase()], // no wallet selected - if EVM address show all evm wallets and tokens in next view message: opts?.message || '', + feePerKb: opts?.feePerKb, }, }, }, @@ -556,7 +555,7 @@ export const goToAmount = chain, recipient, wallet, - opts: urlOpts, + opts, }: { coin: string; chain: string; @@ -585,8 +584,10 @@ export const goToAmount = ...recipient, ...{ opts: { - showERC20Tokens: - !!BitpaySupportedEvmCoins[recipient.currency.toLowerCase()], // no wallet selected - if ETH address show token wallets in next view + showEVMWalletsAndTokens: + !!BitpaySupportedEvmCoins[recipient.currency.toLowerCase()], // no wallet selected - if EVM address show all evm wallets and tokens in next view + message: opts?.message || '', + feePerKb: opts?.feePerKb, }, }, }, @@ -607,7 +608,7 @@ export const goToAmount = amount: Number(amount), wallet, setButtonState, - opts: {...urlOpts, ...amountOpts}, + opts: {...opts, ...amountOpts}, }), ); }, @@ -755,7 +756,7 @@ const handleBitcoinCashUriLegacyAddress = dispatch => { dispatch( LogActions.info( - '[scan] Incoming-data: Bitcoin Cash URI with legacy address', + '[scan] Incoming-data: BitcoinCash URI with legacy address', ), ); const coin = 'bch'; @@ -899,7 +900,7 @@ const handleRippleUri = currency: coin, chain, address, - destinationTag: Number(destinationTag), + destinationTag: destinationTag ? Number(destinationTag) : undefined, }; if (!amountParam.exec(data)) { dispatch(goToAmount({coin, chain, recipient, wallet})); @@ -1340,7 +1341,7 @@ const handlePlainAddress = dispatch(LogActions.info(`[scan] Incoming-data: ${coin} plain address`)); const network = Object.keys(bitcoreLibs).includes(coin) ? GetAddressNetwork(address, coin as keyof BitcoreLibs) - : undefined; // There is no way to tell if an eth address is goerli or livenet so let's skip the network filter + : undefined; // There is no way to tell if an evm address is goerli or livenet so let's skip the network filter const recipient = { type: opts?.context || 'address', name: opts?.name, @@ -1354,14 +1355,21 @@ const handlePlainAddress = dispatch(goToAmount({coin, chain, recipient, wallet: opts?.wallet})); }; -const goToImport = (importQrCodeData: string): void => { - navigationRef.navigate('Wallet', { - screen: WalletScreens.IMPORT, - params: { - importQrCodeData, - }, - }); -}; +const goToImport = + (importQrCodeData: string): Effect => + (dispatch, getState) => { + dispatch( + LogActions.info( + '[scan] Incoming-data (redirect): QR code export feature', + ), + ); + navigationRef.navigate('Wallet', { + screen: WalletScreens.IMPORT, + params: { + importQrCodeData, + }, + }); + }; const goToJoinWallet = (data: string): Effect => diff --git a/src/store/scan/scan.spec.ts b/src/store/scan/scan.spec.ts new file mode 100644 index 000000000..93b483b75 --- /dev/null +++ b/src/store/scan/scan.spec.ts @@ -0,0 +1,1177 @@ +import * as logActions from '../log/log.actions'; +import * as appActions from '../app/app.actions'; +import * as Root from '../../Root'; +import * as PayPro from '../wallet/effects/paypro/paypro'; +import {incomingData} from './scan.effects'; +import configureTestStore from '@test/store'; +import axios from 'axios'; +import {BwcProvider} from '@/lib/bwc'; +import { + GetAddressNetwork, + bitcoreLibs, +} from '../wallet/effects/address/address'; +import {BitpaySupportedEvmCoins} from '@/constants/currencies'; +import {startCreateKey} from '../wallet/effects'; + +/** + * incomingData Tests + */ +describe('incomingData', () => { + beforeEach(() => { + jest.clearAllMocks(); + }); + + it.skip('Should handle plain text', async () => { + const data = ['Gabriel was here']; + const store = configureTestStore({}); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([false]); // TODO: should be true when we add a text handler + }); + + it('Should handle Join Wallet without created keys', async () => { + const data = + '9QRqDLbtasN5Wd37tRag7TKxMJVnCc2979pgs5CEBmGRmYU7kNrVynHdNtuBYxgfNgdj3EEJkHLbtc'; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promiseResult = await store.dispatch(incomingData(data)); + expect(loggerSpy).toHaveBeenCalledWith( + '[scan] Incoming-data (redirect): Code to join to a multisig wallet', + ); + expect(promiseResult).toBe(true); + expect(navigationSpy).toHaveBeenCalledWith('Wallet', { + params: { + invitationCode: data, + }, + screen: 'JoinMultisig', + }); + }); + + it('Should handle Join Wallet with one created key', async () => { + const data = + '9QRqDLbtasN5Wd37tRag7TKxMJVnCc2979pgs5CEBmGRmYU7kNrVynHdNtuBYxgfNgdj3EEJkHLbtc'; + const store = configureTestStore({}); + const key = await store.dispatch( + startCreateKey([ + {chain: 'btc', currencyAbbreviation: 'btc', isToken: false}, + ]), + ); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promiseResult = await store.dispatch(incomingData(data)); + expect(loggerSpy).toHaveBeenCalledWith( + '[scan] Incoming-data (redirect): Code to join to a multisig wallet', + ); + expect(promiseResult).toBe(true); + expect(navigationSpy).toHaveBeenCalledWith('Wallet', { + params: { + key, + invitationCode: data, + }, + screen: 'JoinMultisig', + }); + }); + + it('Should handle Join Wallet with more than one created key', async () => { + const data = + '9QRqDLbtasN5Wd37tRag7TKxMJVnCc2979pgs5CEBmGRmYU7kNrVynHdNtuBYxgfNgdj3EEJkHLbtc'; + const store = configureTestStore({}); + await store.dispatch( + startCreateKey([ + {chain: 'btc', currencyAbbreviation: 'btc', isToken: false}, + ]), + ); + await store.dispatch( + startCreateKey([ + {chain: 'btc', currencyAbbreviation: 'btc', isToken: false}, + ]), + ); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promiseResult = await store.dispatch(incomingData(data)); + expect(loggerSpy).toHaveBeenCalledWith( + '[scan] Incoming-data (redirect): Code to join to a multisig wallet', + ); + expect(promiseResult).toBe(true); + expect(navigationSpy).toHaveBeenNthCalledWith(1, 'Wallet', { + params: { + onKeySelect: expect.any(Function), + }, + screen: 'KeyGlobalSelect', + }); + }, 10000); + + it('Should handle old Join Wallet', async () => { + const data = + 'RTpopkn5KBnkxuT7x4ummDKx3Lu1LvbntddBC4ssDgaqP7DkojT8ccxaFQEXY4f3huFyMewhHZLbtc'; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const promiseResult = await store.dispatch(incomingData(data)); + expect(loggerSpy).toHaveBeenCalledWith( + '[scan] Incoming-data (redirect): Code to join to a multisig wallet', + ); + expect(promiseResult).toBe(true); + }); + + it('Should handle QR Code Export feature', async () => { + const data = [ + "1|sick arch glare wheat anchor innocent garbage tape raccoon already obey ability|testnet|m/44'/1'/0'|false", + '2|', + '3|', + '1|sick arch glare wheat anchor innocent garbage tape raccoon already obey ability|null|null|false|null', + ]; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true, true, true]); + for (let i = 0; i < 3; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data (redirect): QR code export feature', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + importQrCodeData: data[i], + }, + screen: 'Import', + }); + } + }); + + it('Should handle BitPay Invoices with different coins', async () => { + const data = [ + 'bitcoin:?r=https://bitpay.com/i/CtcM753gnZ4Wpr5pmXU6i9', + 'bitcoincash:?r=https://bitpay.com/i/Rtz1RwWA7kdRRU3Wyo4YDY', + 'ethereum:?r=https://test.bitpay.com/i/VPDDwaG7eaGvFtbyDBq8NR', + ]; + const mockPayProOptions: PayPro.PayProOptions = { + paymentId: '10', + time: '10', + expires: '2019-11-05T16:29:31.754Z', + memo: 'Payment request for BitPay invoice FQLHDgV8YWoy4vT8n4pKQe for merchant Johnco', + payProUrl: 'https://test.bitpay.com/i/FQLHDgV8YWoy4vT8n4pKQe', + paymentOptions: [ + { + chain: 'BTC', + currency: 'BTC', + decimals: 8, + estimatedAmount: 10800, + minerFee: 100, + network: 'testnet', + requiredFeeRate: 1, + selected: false, + }, + { + chain: 'BCH', + currency: 'BCH', + decimals: 8, + estimatedAmount: 339800, + minerFee: 0, + network: 'testnet', + requiredFeeRate: 1, + selected: false, + }, + { + chain: 'ETH', + currency: 'ETH', + decimals: 18, + estimatedAmount: 5255000000000000, + minerFee: 0, + network: 'testnet', + requiredFeeRate: 4000000000, + selected: false, + }, + { + chain: 'ETH', + currency: 'USDC', + decimals: 6, + estimatedAmount: 1000000, + minerFee: 0, + network: 'testnet', + requiredFeeRate: 4000000000, + selected: false, + }, + { + chain: 'ETH', + currency: 'GUSD', + decimals: 2, + estimatedAmount: 100, + minerFee: 0, + network: 'testnet', + requiredFeeRate: 4000000000, + selected: false, + }, + { + chain: 'ETH', + currency: 'PAX', + decimals: 18, + estimatedAmount: 1000000000000000000, + minerFee: 0, + network: 'testnet', + requiredFeeRate: 4000000000, + selected: false, + }, + ], + verified: true, + }; + const store = configureTestStore({}); + const mockData = {data: 'Your mock response data'}; // Your mock response + (axios.get as jest.Mock).mockResolvedValue(mockData); + const loggerSpy = jest.spyOn(logActions, 'info'); + const payproSpy = jest.spyOn(PayPro, 'GetPayProOptions'); + ( + payproSpy as jest.MockedFunction + ).mockImplementation(() => () => Promise.resolve(mockPayProOptions)); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true, true]); + for (let i = 0; i < 3; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: Payment Protocol request', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + payProOptions: mockPayProOptions, + }, + screen: 'PayProConfirm', + }); + } + }); + + it('Should handle BitPay Invoice error', async () => { + const data = ['bitcoin:?r=https://bitpay.com/i/CtcM753gnZ4Wpr5pmXU6i9']; + const store = configureTestStore({}); + const mockData = {data: 'Your mock response data'}; // Your mock response + (axios.get as jest.Mock).mockResolvedValue(mockData); + const loggerSpy = jest.spyOn(logActions, 'info'); + const payproSpy = jest.spyOn(PayPro, 'GetPayProOptions'); + ( + payproSpy as jest.MockedFunction + ).mockImplementation(() => () => { + throw new Error('paypro error'); + }); + const showBottomNotificationModalSpy = jest.spyOn( + appActions, + 'showBottomNotificationModal', + ); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true]); + expect(loggerSpy).toHaveBeenCalledWith( + '[scan] Incoming-data: Payment Protocol request', + ); + expect(showBottomNotificationModalSpy).toHaveBeenCalledWith({ + type: 'warning', + title: 'Something went wrong', + message: 'paypro error', + enableBackdropDismiss: true, + actions: [ + { + text: 'OK', + action: expect.any(Function), + primary: true, + }, + ], + }); + }); + + it('Should handle Bitcoin cash Copay/BitPay format and CashAddr format plain Address', async () => { + const data = [ + 'qr00upv8qjgkym8zng3f663n9qte9ljuqqcs8eep5w', + 'CcnxtMfvBHGTwoKGPSuezEuYNpGPJH6tjN', + ]; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true]); + for (let i = 0; i < 2; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: bch plain address', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: data[i], + chain: 'bch', + currency: 'bch', + destinationTag: undefined, + email: undefined, + name: undefined, + network: 'livenet', + opts: { + feePerKb: undefined, + message: '', + showEVMWalletsAndTokens: false, + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle ETH plain Address', async () => { + const data = ['0xb506c911deE6379e3d4c4d0F4A429a70523960Fd']; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true]); + for (let i = 0; i < 1; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: eth plain address', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: data[i], + chain: 'eth', + currency: 'eth', + destinationTag: undefined, + email: undefined, + name: undefined, + network: undefined, // for showing testnet and livenet wallets + opts: { + showEVMWalletsAndTokens: true, + feePerKb: undefined, + message: '', + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle MATIC plain Address', async () => { + const data = ['0x0be264522706C703a2c6dDb61488F309a510eA26']; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true]); + for (let i = 0; i < 1; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: eth plain address', // yes eth... for now + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: data[i], + chain: 'eth', // yes eth... for now + currency: 'eth', // yes eth... for now + destinationTag: undefined, + email: undefined, + name: undefined, + network: undefined, // for showing testnet and livenet wallets + opts: { + showEVMWalletsAndTokens: true, + feePerKb: undefined, + message: '', + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle XRP plain Address', async () => { + const data = ['rh3VLyj1GbQjX7eA15BwUagEhSrPHmLkSR']; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true]); + for (let i = 0; i < 1; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: xrp plain address', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: data[i], + chain: 'xrp', + currency: 'xrp', + destinationTag: undefined, + email: undefined, + name: undefined, + network: undefined, // for showing testnet and livenet wallets + opts: { + showEVMWalletsAndTokens: false, + feePerKb: undefined, + message: '', + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle DOGECOIN plain Address', async () => { + const data = ['DQmgVRe3RJLz6UNoy1hkjuKdYCWCP6VXSW']; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true]); + for (let i = 0; i < 1; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: doge plain address', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: data[i], + chain: 'doge', + currency: 'doge', + destinationTag: undefined, + email: undefined, + name: undefined, + network: 'livenet', + opts: { + showEVMWalletsAndTokens: false, + feePerKb: undefined, + message: '', + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle LITECOIN plain Address', async () => { + const data = [ + 'LMbBz1rbFoXfBTBEdtTGHq1mk4r6iKnHze', + 'ltc1qesyhcljmtnfge44j7kcc0jvqxzcy4r4gz84m9l3etym2kndqwtxsakkxma', + ]; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true]); + for (let i = 0; i < 2; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: ltc plain address', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: data[i], + chain: 'ltc', + currency: 'ltc', + destinationTag: undefined, + email: undefined, + name: undefined, + network: 'livenet', + opts: { + showEVMWalletsAndTokens: false, + feePerKb: undefined, + message: '', + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle Bitcoin cash Copay/BitPay format and CashAddr format URI', async () => { + const data = [ + 'bitcoincash:CcnxtMfvBHGTwoKGPSuezEuYNpGPJH6tjN', + 'bitcoincash:qr00upv8qjgkym8zng3f663n9qte9ljuqqcs8eep5w', + 'bchtest:pzpaleegjrc0cffrmh3nf43lt3e3gu8awqyxxjuew3', + ]; + + const expected = [ + { + log: '[scan] Incoming-data: BitcoinCash URI', + network: 'livenet', + }, + { + log: '[scan] Incoming-data: bch plain address', + network: 'livenet', + }, + { + log: '[scan] Incoming-data: bch plain address', + network: 'testnet', + }, + ]; + + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true, true]); + for (let i = 0; i < 3; i++) { + const parsed = BwcProvider.getInstance().getBitcoreCash().URI(data[i]); + let addr = parsed.address ? parsed.address.toString() : ''; + + // keep address in original format + if (parsed.address && data[i].indexOf(addr) < 0) { + addr = parsed.address.toCashAddress(); + } + expect(loggerSpy).toHaveBeenNthCalledWith(i + 1, expected[i].log); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: addr, + chain: 'bch', + currency: 'bch', + network: expected[i].network, // for showing testnet and livenet wallets + opts: { + showEVMWalletsAndTokens: false, + feePerKb: undefined, + message: '', + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle ETH URI as address if there is no amount', async () => { + const data = ['ethereum:0xb506c911deE6379e3d4c4d0F4A429a70523960Fd']; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true]); + for (let i = 0; i < 1; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: Ethereum URI', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: '0xb506c911deE6379e3d4c4d0F4A429a70523960Fd', + chain: 'eth', + currency: 'eth', + destinationTag: undefined, + email: undefined, + name: undefined, + network: undefined, // for showing testnet and livenet wallets + opts: { + feePerKb: undefined, + message: '', + showEVMWalletsAndTokens: true, + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle XRP URI as address if there is no amount', async () => { + const data = ['ripple:rh3VLyj1GbQjX7eA15BwUagEhSrPHmLkSR']; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true]); + for (let i = 0; i < 1; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: Ripple URI', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: 'rh3VLyj1GbQjX7eA15BwUagEhSrPHmLkSR', + chain: 'xrp', + currency: 'xrp', + destinationTag: undefined, + opts: { + showEVMWalletsAndTokens: false, + feePerKb: undefined, + message: '', + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle ETH URI with amount (value)', async () => { + const data = [ + { + uri: 'ethereum:0xb506c911deE6379e3d4c4d0F4A429a70523960Fd?value=1543000000000000000', + stateParams: { + params: { + amount: 1.543, + context: 'scanner', + recipient: { + address: '0xb506c911deE6379e3d4c4d0F4A429a70523960Fd', + chain: 'eth', + currency: 'eth', + opts: { + message: '', + showEVMWalletsAndTokens: true, + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }, + }, + { + uri: 'ethereum:0xb506c911deE6379e3d4c4d0F4A429a70523960Fd?value=1543000000000000000?gasPrice=0000400000000000000', + stateParams: { + params: { + amount: 1.543, + context: 'scanner', + recipient: { + address: '0xb506c911deE6379e3d4c4d0F4A429a70523960Fd', + chain: 'eth', + currency: 'eth', + opts: { + feePerKb: 400000000000000, + message: '', + showEVMWalletsAndTokens: true, + }, + type: 'address', + }, + }, + screen: 'GlobalSelect', + }, + }, + ]; + + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element.uri))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true]); + for (let i = 0; i < 2; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: Ethereum URI', + ); + expect(navigationSpy).toHaveBeenNthCalledWith( + i + 1, + 'Wallet', + data[i].stateParams, + ); + } + }); + + it('Should handle XRP URI with amount', async () => { + const data = [ + { + uri: 'ripple:rh3VLyj1GbQjX7eA15BwUagEhSrPHmLkSR?amount=15', + stateParams: { + params: { + amount: 15, + context: 'scanner', + recipient: { + address: 'rh3VLyj1GbQjX7eA15BwUagEhSrPHmLkSR', + chain: 'xrp', + currency: 'xrp', + opts: { + message: '', + showEVMWalletsAndTokens: false, + }, + type: 'address', + destinationTag: undefined, + }, + }, + screen: 'GlobalSelect', + }, + }, + { + uri: 'ripple:rh3VLyj1GbQjX7eA15BwUagEhSrPHmLkSR?amount=15&dt=123456', + stateParams: { + params: { + amount: 15, + context: 'scanner', + recipient: { + address: 'rh3VLyj1GbQjX7eA15BwUagEhSrPHmLkSR', + chain: 'xrp', + currency: 'xrp', + opts: { + message: '', + showEVMWalletsAndTokens: false, + }, + type: 'address', + destinationTag: 123456, + }, + }, + screen: 'GlobalSelect', + }, + }, + ]; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element.uri))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true]); + for (let i = 0; i < 2; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: Ripple URI', + ); + expect(navigationSpy).toHaveBeenNthCalledWith( + i + 1, + 'Wallet', + data[i].stateParams, + ); + } + }); + + it('Should handle Bitcoin cash Copay/BitPay format and CashAddr format URI with amount', async () => { + const data = [ + 'BITCOINCASH:QZCY06MXSK7HW0RU4KZWTRKXDS6VF8Y34VRM5SF9Z7?amount=1.00000000', + 'bchtest:pzpaleegjrc0cffrmh3nf43lt3e3gu8awqyxxjuew3?amount=12.00000000', + ]; + + const expected = [ + { + log: '[scan] Incoming-data: BitcoinCash URI', + amount: 1, + network: 'livenet', + }, + { + log: '[scan] Incoming-data: BitcoinCash URI', + amount: 12, + network: 'testnet', + }, + ]; + + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true]); + for (let i = 0; i < 2; i++) { + const parsed = BwcProvider.getInstance().getBitcoreCash().URI(data[i]); + let addr = parsed.address ? parsed.address.toString() : ''; + + // keep address in original format + if (parsed.address && data[i].indexOf(addr) < 0) { + addr = parsed.address.toCashAddress(); + } + expect(loggerSpy).toHaveBeenNthCalledWith(i + 1, expected[i].log); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + amount: expected[i].amount, + context: 'scanner', + recipient: { + address: addr, + chain: 'bch', + currency: 'bch', + network: expected[i].network, + type: 'address', + opts: { + feePerKb: undefined, + message: '', + showEVMWalletsAndTokens: false, + }, + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should handle Bitcoin URI', async () => { + const data = [ + 'bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', // Genesis Bitcoin Address + 'bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?message=test%20message', // Bitcoin Address with message and not amount + 'bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=1.0000', // Bitcoin Address with amount + 'bitcoin:1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa?amount=1.0000&label=Genesis%20Bitcoin%20Address&message=test%20message', // Basic Payment Protocol + ]; + const expected = [ + {amount: undefined}, + {amount: undefined}, + {amount: 1}, + {amount: 1}, + ]; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true, true, true]); + for (let i = 0; i < 4; i++) { + const parsed = BwcProvider.getInstance().getBitcore().URI(data[i]); + const addr = parsed.address ? parsed.address.toString() : ''; + const message = parsed.message || ''; + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: Bitcoin URI', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + amount: expected[i].amount, + context: 'scanner', + recipient: { + address: addr, + chain: 'btc', + currency: 'btc', + network: 'livenet', + type: 'address', + opts: { + showEVMWalletsAndTokens: false, + message, + }, + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should Handle Bitcoin Cash URI with legacy address', async () => { + const data = 'bitcoincash:1ML5KKKrJEHw3fQqhhajQjHWkh3yKhNZpa'; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const result = await store.dispatch(incomingData(data)); + expect(result).toStrictEqual(true); + expect(loggerSpy).toHaveBeenNthCalledWith( + 1, + '[scan] Incoming-data: BitcoinCash URI with legacy address', + ); + expect(loggerSpy).toHaveBeenNthCalledWith( + 2, + '[scan] Legacy Bitcoin Address translated to: bitcoincash:qr00upv8qjgkym8zng3f663n9qte9ljuqqcs8eep5w', + ); + const parsed = BwcProvider.getInstance() + .getBitcore() + .URI(data.replace(/^bitcoincash:/, 'bitcoin:')); + const oldAddr = parsed.address ? parsed.address.toString() : ''; + const a = BwcProvider.getInstance() + .getBitcore() + .Address(oldAddr) + .toObject(); + const addr = BwcProvider.getInstance() + .getBitcoreCash() + .Address.fromObject(a) + .toString(); + + expect(navigationSpy).toHaveBeenCalledWith('Wallet', { + params: { + context: 'scanner', + recipient: { + address: addr, + chain: 'bch', + currency: 'bch', + network: 'livenet', + type: 'address', + opts: { + feePerKb: undefined, + message: '', + showEVMWalletsAndTokens: false, + }, + }, + }, + screen: 'GlobalSelect', + }); + }); + + // TODO: fix this test + it.skip('Should Handle Testnet Bitcoin Cash URI with legacy address', async () => { + const data = 'bchtest:mu7ns6LXun5rQiyTJx7yY1QxTzndob4bhJ'; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const result = await store.dispatch(incomingData(data)); + expect(result).toStrictEqual(true); + expect(loggerSpy).toHaveBeenCalledWith( + '[scan] Incoming-data: BitcoinCash URI with legacy address', + ); + const parsed = BwcProvider.getInstance() + .getBitcore() + .URI(data.replace(/^bchtest:/, 'bitcoin:')); + const oldAddr = parsed.address ? parsed.address.toString() : ''; + const a = BwcProvider.getInstance() + .getBitcore() + .Address(oldAddr) + .toObject(); + const addr = BwcProvider.getInstance() + .getBitcoreCash() + .Address.fromObject(a) + .toString(); + + expect(navigationSpy).toHaveBeenCalledWith('Wallet', { + params: { + context: 'scanner', + recipient: { + address: addr, + chain: 'bch', + currency: 'bch', + network: 'testnet', + type: 'address', + opts: { + feePerKb: undefined, + message: '', + showEVMWalletsAndTokens: false, + }, + }, + }, + screen: 'GlobalSelect', + }); + }); + + it('Should handle any type of address/coin using BitPay URI', async () => { + const extractAddress = (data: string) => { + const address = data.replace(/^[a-z]+:/i, '').replace(/\?.*/, ''); + const params = /([\?\&]+[a-z]+=(\d+([\,\.]\d+)?))+/i; + return address.replace(params, ''); + }; + + const data = [ + 'bitpay:mrNYDWy8ZgmgXVKDb4MM71LmfZWBwGztUK?coin=btc&chain=btc&amount=0.0002&message=asd', + 'bitpay:1HZJoc4ZKMvyAYcYCU1vbmwm3KzZq34EmU?coin=btc&chain=btc&amount=0.0002&message=asd', + 'bitpay:0xDF5C0dd7656bB976aD7285a3Fb80C0F6B9604576?coin=eth&chain=eth&amount=0.0002&message=asd', + 'bitpay:bchtest:qp2gujqu2dsp6zs4kp0pevm2yl8ydx723q2kvfn7tc?coin=bch&chain=bch&amount=0.0002&message=asd', + 'bitpay:bitcoincash:qpcc9qe5ja73k7ekkqrnjfp9tya0r3d5tvpm2yfa0d?coin=bch&chain=bch&amount=0.0002&message=asd', + 'bitpay:rKMATNRkXgxSQMJTpiC4yRNPMMJkz9hjte?coin=xrp&chain=xrp&amount=0.0002&message=asd', + 'bitpay:0xDF5C0dd7656bB976aD7285a3Fb80C0F6B9604576?coin=usdt&chain=eth&amount=0.0002&message=asd&gasPrice=1000000000', + 'bitpay:0x0be264522706C703a2c6dDb61488F309a510eA26?coin=matic&chain=matic&amount=0.0002&message=asd', + 'bitpay:0x0be264522706C703a2c6dDb61488F309a510eA26?coin=usdc&chain=matic&amount=0.0002&message=asd', + ]; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([ + true, + true, + true, + true, + true, + true, + true, + true, + true, + ]); + for (let i = 0; i < 9; i++) { + const address = extractAddress(data[i]); + let params: URLSearchParams = new URLSearchParams( + data[i].replace(`bitpay:${address}`, ''), + ); + const message = params.get('message'); + const coin = params.get('coin')!.toLowerCase(); + const chain = params.get('chain'); + const network = Object.keys(bitcoreLibs).includes(coin) + ? GetAddressNetwork(address, coin as any) + : undefined; + let feePerKb; + if (params.get('gasPrice')) { + feePerKb = Number(params.get('gasPrice')); + } + const showEVMWalletsAndTokens = + !!BitpaySupportedEvmCoins[coin.toLowerCase()]; + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: BitPay URI', + data[i], + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + amount: 0.0002, + context: 'scanner', + recipient: { + address, + chain: chain, + currency: coin, + network, + type: 'address', + opts: { + showEVMWalletsAndTokens, + message, + feePerKb, + }, + }, + }, + screen: 'GlobalSelect', + }); + } + }); + + it('Should not handle BitPay URI if address or coin is missing (ScanPage)', async () => { + const data = [ + 'bitpay:?coin=btc&amount=0.0002&message=asd', + 'bitpay:1HZJoc4ZKMvyAYcYCU1vbmwm3KzZq34EmU?amount=0.0002&message=asd', + 'bitpay:0xDF5C0dd7656bB976aD7285a3Fb80C0F6B9604576?amount=0.0002&message=asd', + 'bitpay:bchtest:qp2gujqu2dsp6zs4kp0pevm2yl8ydx723q2kvfn7tc?amount=0.0002&message=asd', + 'bitpay:?coin=bch&amount=0.0002&message=asd', + ]; + const store = configureTestStore({}); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([false, false, false, false, false]); + }); + + it('Should not handle BitPay URI if address or coin is missing (SendPage)', async () => { + const data = [ + 'bitpay:?coin=btc&amount=0.0002&message=asd', + 'bitpay:1HZJoc4ZKMvyAYcYCU1vbmwm3KzZq34EmU?amount=0.0002&message=asd', + 'bitpay:?coin=eth&amount=0.0002&message=asd', + 'bitpay:bchtest:qp2gujqu2dsp6zs4kp0pevm2yl8ydx723q2kvfn7tc?amount=0.0002&message=asd', + 'bitpay:?coin=bch&amount=0.0002&message=asd', + ]; + const store = configureTestStore({}); + const promises: any[] = []; + const keys = await store.dispatch( + startCreateKey([ + {chain: 'btc', currencyAbbreviation: 'btc', isToken: false}, + ]), + ); + data.forEach(element => + promises.push( + store.dispatch(incomingData(element, {wallet: keys.wallets[0]})), + ), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([false, false, false, false, false]); + }); + + it('Should handle Bitcoin Livenet and Testnet Plain Address', async () => { + const data = [ + '1A1zP1eP5QGefi2DMPTfTL5SLmv7DivfNa', // Genesis Bitcoin Address + 'mpXwg4jMtRhuSpVq4xS3HFHmCmWp9NyGKt', // Genesis Testnet3 Bitcoin Address + ]; + const expected = ['livenet', 'testnet']; + const store = configureTestStore({}); + const loggerSpy = jest.spyOn(logActions, 'info'); + const navigationSpy = jest.spyOn(Root.navigationRef, 'navigate'); + const promises: any[] = []; + data.forEach(element => + promises.push(store.dispatch(incomingData(element))), + ); + const promisesResult = await Promise.all(promises); + expect(promisesResult).toStrictEqual([true, true]); + for (let i = 0; i < 2; i++) { + expect(loggerSpy).toHaveBeenNthCalledWith( + i + 1, + '[scan] Incoming-data: btc plain address', + ); + expect(navigationSpy).toHaveBeenNthCalledWith(i + 1, 'Wallet', { + params: { + context: 'scanner', + recipient: { + address: data[i], + chain: 'btc', + currency: 'btc', + network: expected[i], + type: 'address', + opts: { + feePerKb: undefined, + message: '', + showEVMWalletsAndTokens: false, + }, + }, + }, + screen: 'GlobalSelect', + }); + } + }); +}); diff --git a/src/store/wallet/effects/paypro/paypro.ts b/src/store/wallet/effects/paypro/paypro.ts index 00072e806..4f72a8a77 100644 --- a/src/store/wallet/effects/paypro/paypro.ts +++ b/src/store/wallet/effects/paypro/paypro.ts @@ -22,6 +22,7 @@ export interface PayProOptions { paymentId: string; paymentOptions: PayProPaymentOption[]; payProUrl: string; + verified: boolean; } export const GetPayProOptions = diff --git a/test/setup.js b/test/setup.js index 7af4ecdd8..c24d45cf2 100644 --- a/test/setup.js +++ b/test/setup.js @@ -81,6 +81,11 @@ jest.mock('@react-navigation/native', () => { const actualNav = jest.requireActual('@react-navigation/native'); return { ...actualNav, + createNavigationContainerRef: jest.fn(() => ({ + navigate: jest.fn(), + dispatch: jest.fn(), + addListener: jest.fn(), + })), useNavigation: () => ({ navigate: jest.fn(), dispatch: jest.fn(), @@ -92,6 +97,7 @@ jest.mock('@react-navigation/native', () => { jest.mock('mixpanel-react-native', () => ({ __esModule: true, default: () => jest.fn(), + MixpanelProperties: {}, Mixpanel: jest.fn(() => ({ init: jest.fn(), })), @@ -138,3 +144,9 @@ jest.mock('react-native-localize', () => ({ addEventListener: jest.fn(), removeEventListener: jest.fn(), })); + +jest.mock('axios'); + +jest.mock('i18next', () => ({ + t: key => key, +}));