Merge branch 'master' into encryptprompt

This commit is contained in:
Marcos Rodriguez Velez 2024-08-11 17:29:02 -04:00
commit f46406fe26
7 changed files with 171 additions and 30 deletions

View file

@ -63,10 +63,10 @@
"test": "npm run tslint && npm run lint && npm run unit && npm run jest",
"jest": "jest tests/integration/*",
"e2e:debug-build": "detox build -c android.debug",
"e2e:debug-test": "detox test -c android.debug -d 200000 -l info",
"e2e:debug-test": "detox test -c android.debug -d 200000 --loglevel error --reuse",
"e2e:debug": "(test -f android/app/build/outputs/apk/debug/app-debug.apk && test -f android/app/build/outputs/apk/androidTest/debug/app-debug-androidTest.apk) || npm run e2e:debug-build; npm run e2e:debug-test",
"e2e:release-build": "detox build -c android.release",
"e2e:release-test": "detox test -c android.release",
"e2e:release-test": "detox test -c android.release --loglevel error",
"tslint": "tsc",
"lint": " npm run tslint && node scripts/find-unused-loc.js && eslint --ext .js,.ts,.tsx '*.@(js|ts|tsx)' screen 'blue_modules/*.@(js|ts|tsx)' class models loc tests components navigation typings",
"lint:fix": "npm run lint -- --fix",

View file

@ -405,7 +405,11 @@ const ReceiveDetails = () => {
return (
<>
<ScrollView contentContainerStyle={[styles.root, stylesHook.root]} keyboardShouldPersistTaps="always">
<ScrollView
testID="ReceiveDetailsScrollView"
contentContainerStyle={[styles.root, stylesHook.root]}
keyboardShouldPersistTaps="always"
>
{wallet?.allowBIP47() && wallet.isBIP47Enabled() && (
<View style={styles.tabsContainer}>
<SegmentedControl

View file

@ -168,7 +168,7 @@ export default function PaymentCodesList() {
if (!(await confirm(loc.wallets.details_are_you_sure))) {
return;
}
counterpartyMetadata[pc] = { label: counterpartyMetadata[pc]?.label ?? '', hidden: true };
counterpartyMetadata[pc] = { label: counterpartyMetadata[pc]?.label, hidden: true };
setReload(Math.random());
await saveToDisk();
break;
@ -191,12 +191,12 @@ export default function PaymentCodesList() {
});
};
const renderItem = (pc: string) => {
const renderItem = (pc: string, index: number) => {
if (counterpartyMetadata?.[pc]?.hidden) return null; // hidden contact, do not render
const color = createHash('sha256').update(pc).digest().toString('hex').substring(0, 6);
const displayName = shortenContactName(counterpartyMetadata?.[pc]?.label ?? pc);
const displayName = shortenContactName(counterpartyMetadata?.[pc]?.label || pc);
if (previousRouteName === 'SendDetails') {
return (
@ -204,7 +204,9 @@ export default function PaymentCodesList() {
<View style={styles.contactRowContainer}>
<View style={[styles.circle, { backgroundColor: '#' + color }]} />
<View style={styles.contactRowBody}>
<Text style={[styles.contactRowNameText, { color: colors.labelText }]}>{displayName}</Text>
<Text testID={`ContactListItem${index}`} style={[styles.contactRowNameText, { color: colors.labelText }]}>
{displayName}
</Text>
</View>
</View>
<View style={styles.stick} />
@ -222,7 +224,9 @@ export default function PaymentCodesList() {
<View style={styles.contactRowContainer}>
<View style={[styles.circle, { backgroundColor: '#' + color }]} />
<View style={styles.contactRowBody}>
<Text style={[styles.contactRowNameText, { color: colors.labelText }]}>{displayName}</Text>
<Text testID={`ContactListItem${index}`} style={[styles.contactRowNameText, { color: colors.labelText }]}>
{displayName}
</Text>
</View>
</View>
<View style={styles.stick} />
@ -260,6 +264,7 @@ export default function PaymentCodesList() {
if (cl.isAddressValid(newPc)) {
// this is not a payment code but a regular onchain address. pretending its a payment code and adding it
foundWallet.addBIP47Receiver(newPc);
await saveToDisk();
setReload(Math.random());
return;
}
@ -272,6 +277,7 @@ export default function PaymentCodesList() {
if (cl.isBip352PaymentCodeValid(newPc)) {
// ok its a SilentPayments code, notification tx is not needed, just add it to recipients:
foundWallet.addBIP47Receiver(newPc);
await saveToDisk();
setReload(Math.random());
return;
}
@ -354,7 +360,11 @@ export default function PaymentCodesList() {
<Text>Internal error</Text>
) : (
<View style={styles.sectionListContainer}>
<SectionList sections={data} keyExtractor={(item, index) => item + index} renderItem={({ item }) => renderItem(item)} />
<SectionList
sections={data}
keyExtractor={(item, index) => item + index}
renderItem={({ item, index }) => renderItem(item, index)}
/>
</View>
)}

View file

@ -117,6 +117,7 @@ const WalletDetails = () => {
const [useWithHardwareWallet, setUseWithHardwareWallet] = useState(wallet.useWithHardwareWalletEnabled());
const { isAdvancedModeEnabled } = useSettings();
const [isBIP47Enabled, setIsBIP47Enabled] = useState(wallet.isBIP47Enabled());
const [isContactsVisible, setIsContactsVisible] = useState(wallet.allowBIP47() && wallet.isBIP47Enabled());
const [hideTransactionsInWalletsList, setHideTransactionsInWalletsList] = useState(!wallet.getHideTransactionsInWalletsList());
const { goBack, setOptions, navigate } = useExtendedNavigation();
const { colors } = useTheme();
@ -135,6 +136,10 @@ const WalletDetails = () => {
const onMenuWillShow = () => setIsToolTipMenuVisible(true);
const onMenuWillHide = () => setIsToolTipMenuVisible(false);
useEffect(() => {
setIsContactsVisible(isBIP47Enabled);
}, [isBIP47Enabled]);
useEffect(() => {
if (isAdvancedModeEnabled && wallet.allowMasterFingerprint()) {
InteractionManager.runAfterInteractions(() => {
@ -523,7 +528,7 @@ const WalletDetails = () => {
<Text style={[styles.textLabel2, stylesHook.textLabel2]}>{loc.bip47.payment_code}</Text>
<View style={styles.hardware}>
<BlueText>{loc.bip47.purpose}</BlueText>
<Switch value={isBIP47Enabled} onValueChange={setIsBIP47Enabled} />
<Switch value={isBIP47Enabled} onValueChange={setIsBIP47Enabled} testID="BIP47Switch" />
</View>
</>
) : null}
@ -563,9 +568,9 @@ const WalletDetails = () => {
{(wallet instanceof AbstractHDElectrumWallet || (wallet.type === WatchOnlyWallet.type && wallet.isHd())) && (
<ListItem disabled={isToolTipMenuVisible} onPress={navigateToAddresses} title={loc.wallets.details_show_addresses} chevron />
)}
{wallet.allowBIP47() && wallet.isBIP47Enabled() && (
{isContactsVisible ? (
<ListItem disabled={isToolTipMenuVisible} onPress={navigateToContacts} title={loc.bip47.contacts} chevron />
)}
) : null}
<BlueCard style={styles.address}>
<View>
<BlueSpacing20 />

View file

@ -588,7 +588,7 @@ describe('BlueWallet UI Tests - no wallets', () => {
const feeRate = 3;
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText(feeRate + '');
await element(by.type('android.widget.EditText')).typeText(feeRate + '\n');
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(5000);

View file

@ -1,9 +1,7 @@
import assert from 'assert';
import * as bitcoin from 'bitcoinjs-lib';
import { extractTextFromElementById, hashIt, helperImportWallet, sleep, sup, yo } from './helperz';
let importedSuccessfully = false;
import { extractTextFromElementById, getSwitchValue, hashIt, helperImportWallet, sleep, sup, yo } from './helperz';
/**
* in this suite each test requires that there is one specific wallet present, thus, we import it
@ -21,7 +19,6 @@ beforeAll(async () => {
console.log('before all - importing bip48...');
await helperImportWallet(process.env.HD_MNEMONIC_BIP84, 'HDsegwitBech32', 'Imported HD SegWit (BIP84 Bech32 Native)', '0.00105526');
console.log('...imported!');
importedSuccessfully = true;
await device.pressBack();
await sleep(15000);
}, 1200_000);
@ -36,7 +33,6 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
if (!importedSuccessfully) throw new Error('BIP84 was not imported during the setup');
await device.launchApp({ newInstance: true });
@ -52,7 +48,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
const feeRate = 2;
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText(feeRate + '');
await element(by.type('android.widget.EditText')).typeText(feeRate + '\n');
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(5000);
@ -187,7 +183,6 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
if (!importedSuccessfully) throw new Error('BIP84 was not imported during the setup');
await device.launchApp({ newInstance: true });
@ -203,7 +198,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
const feeRate = 2;
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).replaceText(feeRate + '');
await element(by.type('android.widget.EditText')).typeText(feeRate + '\n');
await element(by.text('OK')).tap();
// lest add another two outputs
@ -262,7 +257,6 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
if (!importedSuccessfully) throw new Error('BIP84 was not imported during the setup');
await device.launchApp({ newInstance: true });
@ -275,7 +269,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
const feeRate = 2;
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText(feeRate + '');
await element(by.type('android.widget.EditText')).typeText(feeRate + '\n');
await element(by.text('OK')).tap();
// first send MAX output
@ -333,7 +327,6 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
if (!importedSuccessfully) throw new Error('BIP84 was not imported during the setup');
await device.launchApp({ newInstance: true });
@ -369,6 +362,129 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('payment codes & manage contacts', async () => {
const lockFile = '/tmp/travislock.' + hashIt('t_manage_contacts');
if (process.env.TRAVIS) {
if (require('fs').existsSync(lockFile)) return console.warn('skipping 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();
// switch on BIP47 slider if its not switched
if (!(await getSwitchValue('BIP47Switch'))) {
await expect(element(by.text('Contacts'))).not.toBeVisible();
await element(by.id('BIP47Switch')).tap();
await expect(element(by.text('Contacts'))).toBeVisible();
await element(by.text('Save')).tap(); // automatically goes back 1 screen
await element(by.text('OK')).tap();
} else {
await device.pressBack();
}
// go to receive screen and check that payment code is there
await element(by.id('ReceiveButton')).tap();
try {
await element(by.text('ASK ME LATER.')).tap();
} catch (_) {}
await element(by.text('Payment Code')).tap();
await element(by.id('ReceiveDetailsScrollView')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
await expect(
element(
by.text('PM8TJbcHbQFgBL5mAYUCxJEhsz8F66abWAnVqiq6Pa8Rav8qG6XjaJQmSzNqgc1k63ipiEnobNpAoxNJVzRkdoUEANj9KyBEjLt4hL99RMoa8iJXwwwM'),
),
).toBeVisible();
// now, testing contacts list
await device.pressBack();
await device.pressBack();
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
await element(by.id('WalletDetails')).tap();
await element(by.id('WalletDetailsScroll')).swipe('up', 'fast', 1); // in case emu screen is small and it doesnt fit
await element(by.text('Contacts')).tap();
await expect(element(by.text('Add Contact'))).toBeVisible();
await expect(element(by.id('ContactListItem0'))).not.toBeVisible();
await element(by.text('Add Contact')).tap();
await element(by.type('android.widget.EditText')).replaceText('13HaCAB4jf7FYSZexJxoczyDDnutzZigjS');
await sleep(1000);
await element(by.text('OK')).tap();
await element(by.text('Add Contact')).tap();
await element(by.type('android.widget.EditText')).replaceText(
'sp1qqgste7k9hx0qftg6qmwlkqtwuy6cycyavzmzj85c6qdfhjdpdjtdgqjuexzk6murw56suy3e0rd2cgqvycxttddwsvgxe2usfpxumr70xc9pkqwv',
);
await element(by.text('OK')).tap();
await expect(element(by.id('ContactListItem0'))).toBeVisible();
await expect(element(by.id('ContactListItem1'))).toBeVisible();
await element(by.text('Add Contact')).tap();
await element(by.type('android.widget.EditText')).replaceText(
'PM8TJS2JxQ5ztXUpBBRnpTbcUXbUHy2T1abfrb3KkAAtMEGNbey4oumH7Hc578WgQJhPjBxteQ5GHHToTYHE3A1w6p7tU6KSoFmWBVbFGjKPisZDbP97',
);
await element(by.text('OK')).tap();
await sup('On-chain transaction needed');
await element(by.text('Cancel')).tap();
// testing renaming contact:
await element(by.id('ContactListItem0')).tap();
await element(by.text('Rename contact')).tap();
await element(by.type('android.widget.EditText')).replaceText('c0ntact');
await element(by.text('OK')).tap();
await expect(element(by.text('c0ntact'))).toBeVisible();
// now, doing a real transaction with our contacts
await device.pressBack();
await device.pressBack();
await device.pressBack();
await element(by.text('Imported HD SegWit (BIP84 Bech32 Native)')).tap();
await element(by.id('SendButton')).tap();
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.text('Insert Contact')).tap();
await element(by.id('ContactListItem0')).tap();
await element(by.id('BitcoinAmountInput')).typeText('0.0001\n');
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.text('Add Recipient')).tap();
await element(by.id('advancedOptionsMenuButton')).tap();
await element(by.text('Insert Contact')).tap();
await element(by.id('ContactListItem1')).tap();
await element(by.id('BitcoinAmountInput')).atIndex(1).typeText('0.0002\n');
await sleep(1000);
// setting fee rate:
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText('1\n');
await element(by.text('OK')).tap();
await sleep(1000);
await element(by.id('CreateTransactionButton')).tap();
await element(by.id('TransactionDetailsButton')).tap();
const txhex1 = await extractTextFromElementById('TxhexInput');
const tx1 = bitcoin.Transaction.fromHex(txhex1);
assert.strictEqual(tx1.outs.length, 3);
assert.strictEqual(tx1.outs[0].script.toString('hex'), '76a91419129d53e6319baf19dba059bead166df90ab8f588ac');
assert.strictEqual(tx1.outs[0].value, 10000);
assert.strictEqual(tx1.outs[1].script.toString('hex'), '5120b81959cd9a4954cd525916cd636b4ffe9466600412ccd162653a0f464489f1a8');
assert.strictEqual(tx1.outs[1].value, 20000);
process.env.TRAVIS && require('fs').writeFileSync(lockFile, '1');
});
it('can do basic wallet-details operations', async () => {
const lockFile = '/tmp/travislock.' + hashIt('t_walletdetails');
if (process.env.TRAVIS) {
@ -378,7 +494,6 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
if (!importedSuccessfully) throw new Error('BIP84 was not imported during the setup');
await device.launchApp({ newInstance: true });
@ -429,7 +544,6 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
if (!importedSuccessfully) throw new Error('BIP84 was not imported during the setup');
await device.launchApp({ newInstance: true });
@ -442,7 +556,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
const feeRate = 2;
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText(feeRate + '');
await element(by.type('android.widget.EditText')).typeText(feeRate + '\n');
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(5000);
@ -467,7 +581,6 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
console.error('process.env.HD_MNEMONIC_BIP84 not set, skipped');
return;
}
if (!importedSuccessfully) throw new Error('BIP84 was not imported during the setup');
await device.launchApp({ newInstance: true });
// go inside the wallet
@ -515,7 +628,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
// setting fee rate:
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText('1');
await element(by.type('android.widget.EditText')).typeText('1\n');
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(5000);
await element(by.id('CreateTransactionButton')).tap();
@ -544,7 +657,7 @@ describe('BlueWallet UI Tests - import BIP84 wallet', () => {
// setting fee rate:
await element(by.id('chooseFee')).tap();
await element(by.id('feeCustom')).tap();
await element(by.type('android.widget.EditText')).typeText('1');
await element(by.type('android.widget.EditText')).typeText('1\n');
await element(by.text('OK')).tap();
if (process.env.TRAVIS) await sleep(5000);
await element(by.id('CreateTransactionButton')).tap();

View file

@ -12,6 +12,15 @@ export function sup(text, timeout = 33000) {
.withTimeout(timeout);
}
export async function getSwitchValue(switchId) {
try {
await expect(element(by.id(switchId))).toHaveToggleValue(true);
return true;
} catch (_) {
return false;
}
}
export async function helperImportWallet(importText, walletType, expectedWalletLabel, expectedBalance, passphrase) {
await yo('WalletsList');