Merge branch 'master' into headr

This commit is contained in:
Marcos Rodriguez Velez 2025-02-28 18:00:59 -04:00
commit 8d694ceb7b
16 changed files with 325 additions and 123 deletions

View file

@ -73,6 +73,10 @@ def enableProguardInReleaseBuilds = false
def jscFlavor = 'org.webkit:android-jsc-intl:+'
android {
androidResources {
noCompress += ["bundle"]
}
ndkVersion rootProject.ext.ndkVersion
buildToolsVersion rootProject.ext.buildToolsVersion
compileSdkVersion rootProject.ext.compileSdkVersion
@ -136,4 +140,4 @@ dependencies {
implementation 'androidx.constraintlayout:constraintlayout:2.2.0'
}
apply plugin: 'com.google.gms.google-services' // Google Services plugin
apply plugin: "com.bugsnag.android.gradle"
apply plugin: "com.bugsnag.android.gradle"

View file

@ -10,7 +10,6 @@ import RNQRGenerator from 'rn-qr-generator';
import { CommonToolTipActions } from '../typings/CommonToolTipActions';
import { useSettings } from '../hooks/context/useSettings';
import { useExtendedNavigation } from '../hooks/useExtendedNavigation';
import { isDesktop } from '../blue_modules/environment';
interface AddressInputScanButtonProps {
isLoading?: boolean;
@ -146,7 +145,6 @@ export const AddressInputScanButton = ({
testID={testID}
disabled={isLoading}
onPress={toolTipOnPress}
isMenuPrimaryAction={isDesktop}
buttonStyle={type === 'default' ? buttonStyle : undefined}
accessibilityLabel={loc.send.details_scan}
accessibilityHint={loc.send.details_scan_hint}

View file

@ -1,10 +1,13 @@
import React, { useState, useRef } from 'react';
import { Animated, Platform, StyleSheet, TouchableOpacity, View } from 'react-native';
// @ts-ignore: no declaration file yet
import { Camera, CameraApi, CameraType, Orientation } from 'react-native-camera-kit';
import loc from '../loc';
import { Icon } from '@rneui/base';
import { OnOrientationChangeData, OnReadCodeData } from 'react-native-camera-kit/dist/CameraProps';
import { triggerSelectionHapticFeedback } from '../blue_modules/hapticFeedback';
import { isDesktop } from '../blue_modules/environment';
// @ts-ignore: no declaration file yet
import { OnOrientationChangeData, OnReadCodeData } from 'react-native-camera-kit/dist/CameraProps';
interface CameraScreenProps {
onCancelButtonPress: () => void;
@ -95,74 +98,104 @@ const CameraScreen: React.FC<CameraScreenProps> = ({
return (
<View style={styles.screen}>
<View style={styles.topButtons}>
<TouchableOpacity style={[styles.topButton, uiRotationStyle, torchMode ? styles.activeTorch : {}]} onPress={onSetTorch}>
<Animated.View style={styles.topButtonImg}>
{Platform.OS === 'ios' ? (
<Icon name={torchMode ? 'flashlight-on' : 'flashlight-off'} type="font-awesome-6" color={torchMode ? '#000' : '#fff'} />
) : (
<Icon name={torchMode ? 'flash-on' : 'flash-off'} type="ionicons" color={torchMode ? '#000' : '#fff'} />
{/* Render top buttons only if not desktop as they would not be relevant */}
{!isDesktop && (
<View style={styles.topButtons}>
<TouchableOpacity style={[styles.topButton, uiRotationStyle, torchMode ? styles.activeTorch : {}]} onPress={onSetTorch}>
<Animated.View style={styles.topButtonImg}>
{Platform.OS === 'ios' ? (
<Icon name={torchMode ? 'flashlight-on' : 'flashlight-off'} type="font-awesome-6" color={torchMode ? '#000' : '#fff'} />
) : (
<Icon name={torchMode ? 'flash-on' : 'flash-off'} type="ionicons" color={torchMode ? '#000' : '#fff'} />
)}
</Animated.View>
</TouchableOpacity>
<View style={styles.rightButtonsContainer}>
{showImagePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_image}
style={[styles.topButton, styles.spacing, uiRotationStyle]}
onPress={onImagePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="image" type="font-awesome" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
</Animated.View>
</TouchableOpacity>
<View style={styles.rightButtonsContainer}>
{showImagePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_image}
style={[styles.topButton, styles.spacing, uiRotationStyle]}
onPress={onImagePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="image" type="font-awesome" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
{showFilePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_file}
style={[styles.topButton, styles.spacing, uiRotationStyle]}
onPress={onFilePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
{showFilePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_file}
style={[styles.topButton, styles.spacing, uiRotationStyle]}
onPress={onFilePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
</View>
</View>
</View>
)}
<View style={styles.cameraContainer}>
<Camera
ref={cameraRef}
style={styles.cameraPreview}
cameraType={cameraType}
resetFocusWhenMotionDetected
zoom={zoom}
maxZoom={10}
scanBarcode
resizeMode="cover"
onZoom={handleZoom}
onReadCode={handleReadCode}
torchMode={torchMode ? 'on' : 'off'}
resetFocusWhenMotionDetected
zoom={zoom}
onZoom={handleZoom}
maxZoom={10}
onOrientationChange={handleOrientationChange}
/>
</View>
<View style={styles.bottomButtons}>
<TouchableOpacity onPress={onCancelButtonPress}>
<Animated.Text style={[styles.backTextStyle, uiRotationStyle]}>{loc._.cancel}</Animated.Text>
</TouchableOpacity>
<TouchableOpacity style={[styles.bottomButton, uiRotationStyle]} onPress={onSwitchCameraPressed}>
<Animated.View style={[styles.topButtonImg, uiRotationStyle]}>
{Platform.OS === 'ios' ? (
<Icon name="cameraswitch" type="font-awesome-6" color="#ffffff" />
) : (
<Icon name={cameraType === CameraType.Back ? 'camera-rear' : 'camera-front'} type="ionicons" color="#ffffff" />
{isDesktop ? (
<View style={styles.rightButtonsContainer}>
{showImagePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_image}
style={[styles.bottomButton, styles.spacing, uiRotationStyle]}
onPress={onImagePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="image" type="font-awesome" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
</Animated.View>
</TouchableOpacity>
{showFilePickerButton && (
<TouchableOpacity
accessibilityRole="button"
accessibilityLabel={loc._.pick_file}
style={[styles.bottomButton, styles.spacing, uiRotationStyle]}
onPress={onFilePickerButtonPress}
>
<Animated.View style={styles.topButtonImg}>
<Icon name="file-import" type="font-awesome-5" color="#ffffff" />
</Animated.View>
</TouchableOpacity>
)}
</View>
) : (
<TouchableOpacity style={[styles.bottomButton, uiRotationStyle]} onPress={onSwitchCameraPressed}>
<Animated.View style={[styles.topButtonImg, uiRotationStyle]}>
{Platform.OS === 'ios' ? (
<Icon name="cameraswitch" type="font-awesome-6" color="#ffffff" />
) : (
<Icon name={cameraType === CameraType.Back ? 'camera-rear' : 'camera-front'} type="ionicons" color="#ffffff" />
)}
</Animated.View>
</TouchableOpacity>
)}
</View>
</View>
);

View file

@ -362,6 +362,7 @@ export const TransactionListItem: React.FC<TransactionListItemProps> = React.mem
rightTitle={rowTitle}
rightTitleStyle={rowTitleStyle}
containerStyle={combinedStyle}
testID="TransactionListItem"
/>
</ToolTipMenu>
);

View file

@ -1333,7 +1333,7 @@ PODS:
- react-native-tcp-socket (6.2.0):
- CocoaAsyncSocket
- React-Core
- react-native-true-sheet (1.1.1):
- react-native-true-sheet (2.0.0):
- DoubleConversion
- glog
- hermes-engine
@ -1819,7 +1819,7 @@ PODS:
- ReactCommon/turbomodule/bridging
- ReactCommon/turbomodule/core
- Yoga
- RNScreens (4.9.0):
- RNScreens (4.9.1):
- DoubleConversion
- glog
- hermes-engine
@ -2266,7 +2266,7 @@ SPEC CHECKSUMS:
react-native-screen-capture: 7b6121f529681ed2fde36cdedadd0bb39e9a3796
react-native-secure-key-store: eb45b44bdec3f48e9be5cdfca0f49ddf64892ea6
react-native-tcp-socket: 61379457d7e702e83e28c213b6e085ac079e480f
react-native-true-sheet: 58c0848a4326fd2eb7eedd266ec0f39e7c70e5bd
react-native-true-sheet: 15f8d1bfbf2aceca472b9ba585b4116041d20f34
React-nativeconfig: 67fa7a63ea288cb5b1d0dd2deaf240405fec164f
React-NativeModulesApple: 34b7a4d7441a4ee78d18109ff107c1ccf7c074a9
React-perflogger: d1149037ac466ad2141d4ae541ca16cb73b2343b
@ -2312,7 +2312,7 @@ SPEC CHECKSUMS:
RNRate: 7641919330e0d6688ad885a985b4bd697ed7d14c
RNReactNativeHapticFeedback: 00ba111b82aa266bb3ee1aa576831c2ea9a9dfad
RNReanimated: 66cf0f600a26d2b5e74c6e0b1c77c1ab1f62fc05
RNScreens: ee069f569efb54804334321c916643f8cc9debaf
RNScreens: b3975354ddafe0fb00112a9054898ccf0d92c78e
RNShare: 6204e6a1987ba3e7c47071ef703e5449a0e3548a
RNSVG: a07e14363aa208062c6483bad24a438d5986d490
RNVectorIcons: 182892e7d1a2f27b52d3c627eca5d2665a22ee28

View file

@ -29,6 +29,15 @@ export type SendDetailsParams = {
};
};
export type TNavigation = {
pop: () => void;
navigate: () => void;
};
export type TNavigationWrapper = {
navigation: TNavigation;
};
export type SendDetailsStackParamList = {
SendDetails: SendDetailsParams;
Confirm: {
@ -79,7 +88,7 @@ export type SendDetailsStackParamList = {
};
SelectWallet: {
chainType?: Chain;
onWalletSelect?: (wallet: TWallet, navigation: any) => void;
onWalletSelect?: (wallet: TWallet, navigationWrapper: TNavigationWrapper) => void;
availableWallets?: TWallet[];
noWalletExplanationText?: string;
onChainRequireSend?: boolean;

6
package-lock.json generated
View file

@ -67,7 +67,7 @@
"react-native": "0.76.7",
"react-native-biometrics": "3.0.1",
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442",
"react-native-camera-kit": "14.2.0",
"react-native-camera-kit": "https://github.com/BlueWallet/react-native-camera-kit.git#3193427",
"react-native-crypto": "2.2.0",
"react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c",
"react-native-device-info": "14.0.4",
@ -22093,8 +22093,8 @@
},
"node_modules/react-native-camera-kit": {
"version": "14.2.0",
"resolved": "https://registry.npmjs.org/react-native-camera-kit/-/react-native-camera-kit-14.2.0.tgz",
"integrity": "sha512-rPk/4Ux52/Kc6oIPk0x6NsrvDkeL+kd/GAUJ4xBtTlnmiWjLTgeA2Vjgg9ik03mmyf6rV+LaqaOBT7KejhuHKQ==",
"resolved": "git+ssh://git@github.com/BlueWallet/react-native-camera-kit.git#1e1921223bc9da636f9889d96b03df5f77dc7bf1",
"integrity": "sha512-jwVriBGZai7b4TCM0JXR0xqBY0HPtu2NSQQMETTNLyTjYYqkHEK2uaWkq/GY5B93gbAnTGJ5bRyQAqfWkPjDEw==",
"license": "MIT",
"engines": {
"node": ">=18"

View file

@ -134,7 +134,7 @@
"react-native": "0.76.7",
"react-native-biometrics": "3.0.1",
"react-native-blue-crypto": "github:BlueWallet/react-native-blue-crypto#3cb5442",
"react-native-camera-kit": "14.2.0",
"react-native-camera-kit": "https://github.com/BlueWallet/react-native-camera-kit.git#3193427",
"react-native-crypto": "2.2.0",
"react-native-default-preference": "https://github.com/BlueWallet/react-native-default-preference.git#6338a1f1235e4130b8cfc2dd3b53015eeff2870c",
"react-native-device-info": "14.0.4",

View file

@ -129,14 +129,6 @@ const SendDetails = () => {
return initialFee;
}, [customFee, feePrecalc, networkTransactionFees]);
useEffect(() => {
console.log('send/details - useEffect');
if (wallet) {
setHeaderRightOptions();
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [colors, wallet, isTransactionReplaceable, balance, addresses, isEditable, isLoading]);
useEffect(() => {
// decode route params
const currentAddress = addresses[scrollIndex.current];
@ -209,6 +201,7 @@ const SendDetails = () => {
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [routeParams.uri, routeParams.address, routeParams.addRecipientParams]);
useEffect(() => {
// check if we have a suitable wallet
const suitable = wallets.filter(w => w.chain === Chain.ONCHAIN && w.allowSend());
@ -295,38 +288,38 @@ const SendDetails = () => {
const newFeePrecalc: /* Record<string, any> */ IFee = { ...feePrecalc };
let targets = [];
for (const transaction of addresses) {
if (transaction.amount === BitcoinUnit.MAX) {
// single output with MAX
targets = [{ address: transaction.address }];
break;
}
const value = transaction.amountSats;
if (Number(value) > 0) {
targets.push({ address: transaction.address, value });
} else if (transaction.amount) {
if (btcToSatoshi(transaction.amount) > 0) {
targets.push({ address: transaction.address, value: btcToSatoshi(transaction.amount) });
}
}
}
// if targets is empty, insert dust
if (targets.length === 0) {
targets.push({ address: '36JxaUrpDzkEerkTf1FzwHNE1Hb7cCjgJV', value: 546 });
}
// replace wrong addresses with dump
targets = targets.map(t => {
if (!wallet.isAddressValid(t.address)) {
return { ...t, address: '36JxaUrpDzkEerkTf1FzwHNE1Hb7cCjgJV' };
} else {
return t;
}
});
for (const opt of options) {
let targets = [];
for (const transaction of addresses) {
if (transaction.amount === BitcoinUnit.MAX) {
// single output with MAX
targets = [{ address: transaction.address }];
break;
}
const value = transaction.amountSats;
if (Number(value) > 0) {
targets.push({ address: transaction.address, value });
} else if (transaction.amount) {
if (btcToSatoshi(transaction.amount) > 0) {
targets.push({ address: transaction.address, value: btcToSatoshi(transaction.amount) });
}
}
}
// if targets is empty, insert dust
if (targets.length === 0) {
targets.push({ address: '36JxaUrpDzkEerkTf1FzwHNE1Hb7cCjgJV', value: 546 });
}
// replace wrong addresses with dump
targets = targets.map(t => {
if (!wallet.isAddressValid(t.address)) {
return { ...t, address: '36JxaUrpDzkEerkTf1FzwHNE1Hb7cCjgJV' };
} else {
return t;
}
});
let flag = false;
while (true) {
try {
@ -1158,11 +1151,18 @@ const SendDetails = () => {
[headerRightOnPress, isLoading, headerRightActions],
);
const setHeaderRightOptions = () => {
const setHeaderRightOptions = useCallback(() => {
navigation.setOptions({
headerRight: HeaderRight,
});
};
}, [HeaderRight, navigation]);
useEffect(() => {
console.log('send/details - useEffect');
if (wallet) {
setHeaderRightOptions();
}
}, [colors, wallet, isTransactionReplaceable, balance, addresses, isEditable, isLoading, setHeaderRightOptions]);
const handleRecipientsScroll = (e: NativeSyntheticEvent<NativeScrollEvent>) => {
const contentOffset = e.nativeEvent.contentOffset;

View file

@ -72,7 +72,7 @@ const SelectWallet: React.FC = () => {
const onPress = (item: TWallet) => {
triggerHapticFeedback(HapticFeedbackTypes.Selection);
if (onWalletSelect) {
onWalletSelect(item, { navigation: { pop, navigation: navigation.navigate } });
onWalletSelect(item, { navigation: { pop, navigate: navigation.navigate } });
} else {
// @ts-ignore: fix later
navigation.popTo(previousRouteName, { walletID: item.getID(), merge: true });

View file

@ -367,12 +367,17 @@ const WalletDetails: React.FC = () => {
const purgeTransactions = async () => {
if (backdoorPressed < 10) return setBackdoorPressed(backdoorPressed + 1);
setBackdoorPressed(0);
const msg = 'Transactions purged. Pls go to main screen and back to rerender screen';
const msg = 'Transactions & balances purged. Pls go to main screen and back to rerender screen';
if (wallet.type === HDSegwitBech32Wallet.type) {
wallet._txs_by_external_index = {};
wallet._txs_by_internal_index = {};
presentAlert({ message: msg });
wallet._balances_by_external_index = {};
wallet._balances_by_internal_index = {};
wallet._lastTxFetch = 0;
wallet._lastBalanceFetch = 0;
}
// @ts-expect-error: Need to fix later
@ -381,6 +386,15 @@ const WalletDetails: React.FC = () => {
wallet._hdWalletInstance._txs_by_external_index = {};
// @ts-expect-error: Need to fix later
wallet._hdWalletInstance._txs_by_internal_index = {};
// @ts-expect-error: Need to fix later
wallet._hdWalletInstance._balances_by_external_index = {};
// @ts-expect-error: Need to fix later
wallet._hdWalletInstance._balances_by_internal_index = {};
// @ts-expect-error: Need to fix later
wallet._hdWalletInstance._lastTxFetch = 0;
// @ts-expect-error: Need to fix later
wallet._hdWalletInstance._lastBalanceFetch = 0;
presentAlert({ message: msg });
}
};
@ -528,7 +542,7 @@ const WalletDetails: React.FC = () => {
</View>
</>
<>
<Text onPress={purgeTransactions} style={[styles.textLabel2, stylesHook.textLabel2]}>
<Text onPress={purgeTransactions} style={[styles.textLabel2, stylesHook.textLabel2]} testID="PurgeBackdoorButton">
{loc.transactions.transactions_count.toLowerCase()}
</Text>
<BlueText>{wallet.getTransactions().length}</BlueText>

View file

@ -125,7 +125,9 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
const txs = wallet.getTransactions();
txs.sort((a: { received: string }, b: { received: string }) => +new Date(b.received) - +new Date(a.received));
return txs;
}, [wallet]);
// we use `wallet.getLastTxFetch()` to tell if txs list changed
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [wallet, wallet?.getLastTxFetch()]);
const getTransactions = useCallback((lmt = Infinity): Transaction[] => sortedTransactions.slice(0, lmt), [sortedTransactions]);
@ -177,7 +179,6 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
setFetchFailures(0);
const newTimestamp = Date.now();
setLastFetchTimestamp(newTimestamp);
wallet._lastTxFetch = newTimestamp;
} catch (err) {
setFetchFailures(prev => {
const newFailures = prev + 1;
@ -298,11 +299,6 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
index,
});
const listData: Transaction[] = useMemo(() => {
const transactions = getTransactions(limit);
return transactions;
}, [getTransactions, limit]);
const renderItem = useCallback(
// eslint-disable-next-line react/no-unused-prop-types
({ item }: { item: Transaction }) => {
@ -503,13 +499,14 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
onEndReachedThreshold={0.3}
onEndReached={loadMoreTransactions}
ListFooterComponent={renderListFooterComponent}
data={listData}
data={getTransactions(limit)}
extraData={wallet}
keyExtractor={_keyExtractor}
renderItem={renderItem}
initialNumToRender={10}
contentInset={{ top: HEADER_HEIGHT }}
removeClippedSubviews
testID="TransactionsListView"
contentContainerStyle={{ backgroundColor: colors.background }}
maxToRenderPerBatch={15}
onScroll={Animated.event([{ nativeEvent: { contentOffset: { y: scrollY } } }], { useNativeDriver: true, listener: handleScroll })}

View file

@ -12,8 +12,17 @@ import {
tapAndTapAgainIfTextIsNotVisible,
tapIfTextPresent,
waitForId,
countElements,
} from './helperz';
// if loglevel is set to `error`, this kind of logging will still get through
console.warn = console.log = (...args) => {
let output = '';
args.map(arg => (output += String(arg)));
process.stdout.write(output + '\n');
};
/**
* in this suite each test requires that there is one specific wallet present, thus, we import it
* before anything else.
@ -27,7 +36,7 @@ beforeAll(async () => {
// reinstalling the app just for any case to clean up app's storage
await device.launchApp({ delete: true });
console.log('before all - importing bip48...');
console.log('before all - importing bip84...');
await helperImportWallet(process.env.HD_MNEMONIC_BIP84, 'HDsegwitBech32', 'Imported HD SegWit (BIP84 Bech32 Native)', '0.00105526');
console.log('...imported!');
await device.pressBack();
@ -711,4 +720,54 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can purge txs and balance, then refetch data from tx list screen and see data on screen update', async () => {
const lockFile = '/tmp/travislock.' + hashIt('t24');
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile)) return console.warn('skipping', JSON.stringify('t24'), 'as it previously passed on Travis');
}
if (!process.env.HD_MNEMONIC_BIP84) {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
await device.launchApp({ newInstance: true });
// go inside the wallet
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
await element(by.id('WalletDetails')).tap();
// tapping backdoor button to purge txs and balance:
for (let c = 0; c <= 10; c++) {
await element(by.id('PurgeBackdoorButton')).tap();
await sleep(500);
}
await waitForText('OK');
await tapIfTextPresent('OK');
if (device.getPlatform() === 'ios') {
console.warn('rest of the test is Android only, skipped');
return;
}
await device.pressBack();
// asserting there are no transactions and balance is 0:
await expect(element(by.id('WalletBalance'))).toHaveText('0');
await waitForId('TransactionsListEmpty');
assert.strictEqual(await countElements('TransactionListItem'), 0);
await element(by.id('TransactionsListView')).swipe('down', 'slow'); // pul-to-refresh
// asserting balance and txs loaded:
await waitForText('0.00105526'); // the wait inside allows network request to propagate
await waitFor(element(by.id('TransactionsListEmpty')))
.not.toBeVisible()
.withTimeout(25_000);
await expect(element(by.id('WalletBalance'))).toHaveText('0.00105526');
await expect(element(by.id('TransactionsListEmpty'))).not.toBeVisible();
assert.ok((await countElements('TransactionListItem')) >= 3); // 3 is arbitrary, real txs on screen depend on screen size
});
});

View file

@ -1,5 +1,13 @@
import { hashIt, helperDeleteWallet, helperImportWallet, sleep, waitForId } from './helperz';
// if loglevel is set to `error`, this kind of logging will still get through
console.warn = console.log = (...args) => {
let output = '';
args.map(arg => (output += String(arg)));
process.stdout.write(output + '\n');
};
beforeAll(async () => {
// reinstalling the app just for any case to clean up app's storage
await device.launchApp({ delete: true });

View file

@ -228,3 +228,16 @@ export async function tapIfTextPresent(text) {
} catch (_) {}
// no need to check for visibility, just silently ignore exception if such testID is not present
}
export async function countElements(testId) {
let count = 0;
while (true) {
try {
await expect(element(by.id(testId)).atIndex(count)).toExist();
count++;
} catch (_) {
break;
}
}
return count;
}

View file

@ -1,6 +1,7 @@
import assert from 'assert';
import DeeplinkSchemaMatch from '../../class/deeplink-schema-match';
import { HDSegwitBech32Wallet, LightningCustodianWallet } from '../../class';
jest.mock('../../blue_modules/BlueElectrum', () => {
return {
@ -8,6 +9,15 @@ jest.mock('../../blue_modules/BlueElectrum', () => {
};
});
// helper function that promisifies function with a callback:
const asyncNavigationRouteFor = async function (event) {
return new Promise(function (resolve) {
DeeplinkSchemaMatch.navigationRouteFor(event, navValue => {
resolve(navValue);
});
});
};
describe.each(['', '//'])('unit - DeepLinkSchemaMatch', function (suffix) {
it('hasSchema', () => {
assert.ok(DeeplinkSchemaMatch.hasSchema(`bitcoin:${suffix}12eQ9m4sgAwTSQoNXkRABKhCXCsjm2jdVG`));
@ -324,14 +334,6 @@ describe.each(['', '//'])('unit - DeepLinkSchemaMatch', function (suffix) {
},
];
const asyncNavigationRouteFor = async function (event) {
return new Promise(function (resolve) {
DeeplinkSchemaMatch.navigationRouteFor(event, navValue => {
resolve(navValue);
});
});
};
for (const event of events) {
const navValue = await asyncNavigationRouteFor(event.argument);
assert.deepStrictEqual(navValue, event.expected);
@ -493,4 +495,68 @@ describe.each(['', '//'])('unit - DeepLinkSchemaMatch', function (suffix) {
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isNotAllowed1), false);
assert.strictEqual(DeeplinkSchemaMatch.hasNeededJsonKeysForMultiSigSharing(isNotAllowed2), false);
});
it('onWalletSelect should work', async () => {
const response = await asyncNavigationRouteFor({
url: 'bitcoin:BC1QR7P8NSYPZEJY4KP7CJS0HL5T9X0VF3AYF6UQPC?amount=0.00185579&lightning=LNBC1855790N1PNUPWSFPP5P5RVQJA067PV6NJQ3EFKLP78TN6MHUK842ZFGDCTXRDSGNTY765QDZ62PSKJEPQW3HJQSNPD36XJCEQFPHKUETEVFSKGEM9WGSRYVPJXSSZSNMJV3JHYGZFGSAZQARFVD4K2AR5V95KCMMJ9YCQZPUXQZ6GSP53E4EX9YTD2MGDN2C2CFA0J0SM3E7PVLPJ208H5LMYPNJMGZ7RLGS9QXPQYSGQ6GQMEQXJKKF2DHXJK8XQ4WGLM5NTE3RKEXGYQC6HYGFKS9SHHA6HL9X4339MXHNNQFSH7TS62PU8T9RSWTK6HQ4LV4GW3DPD25DQ8UQQYC909N',
});
assert.ok(response[1].onWalletSelect);
let popWasCalled = false;
let navigateWasCalled = false;
let popWasCalled2 = false;
let navigateWasCalled2 = false;
const lw = new LightningCustodianWallet();
const bw = new HDSegwitBech32Wallet();
// navigation for a case when user selected LN wallet when was given a choice
const navigationMock = {
pop: () => {
popWasCalled = true;
// console.log('pop called');
},
navigate: (...args) => {
navigateWasCalled = true;
assert.deepStrictEqual(args, [
'ScanLndInvoiceRoot',
{
params: {
uri: 'lightning:LNBC1855790N1PNUPWSFPP5P5RVQJA067PV6NJQ3EFKLP78TN6MHUK842ZFGDCTXRDSGNTY765QDZ62PSKJEPQW3HJQSNPD36XJCEQFPHKUETEVFSKGEM9WGSRYVPJXSSZSNMJV3JHYGZFGSAZQARFVD4K2AR5V95KCMMJ9YCQZPUXQZ6GSP53E4EX9YTD2MGDN2C2CFA0J0SM3E7PVLPJ208H5LMYPNJMGZ7RLGS9QXPQYSGQ6GQMEQXJKKF2DHXJK8XQ4WGLM5NTE3RKEXGYQC6HYGFKS9SHHA6HL9X4339MXHNNQFSH7TS62PU8T9RSWTK6HQ4LV4GW3DPD25DQ8UQQYC909N',
walletID: 'bfcacb7288cf43c6c02a1154c432ec155b813798fa4e87cd2c1e5531d6363f71',
},
screen: 'ScanLndInvoice',
},
]);
},
};
// navigation for a case when user selected ONCHAIN wallet when was given a choice
const navigationMock2 = {
pop: () => {
popWasCalled2 = true;
},
navigate: (...args) => {
navigateWasCalled2 = true;
assert.deepStrictEqual(args, [
'SendDetailsRoot',
{
params: {
uri: 'bitcoin:BC1QR7P8NSYPZEJY4KP7CJS0HL5T9X0VF3AYF6UQPC?amount=0.00185579&',
walletID: 'a1c50c266e229bb66aca0221d5b6a116720004c97437a0a6e279cfea027d0c87',
},
screen: 'SendDetails',
},
]);
},
};
response[1].onWalletSelect(lw, { navigation: navigationMock });
response[1].onWalletSelect(bw, { navigation: navigationMock2 });
assert.ok(popWasCalled);
assert.ok(navigateWasCalled);
assert.ok(popWasCalled2);
assert.ok(navigateWasCalled2);
});
});