FIX: transactions list screen would not always update with new transactions

This commit is contained in:
overtorment 2025-02-27 12:15:07 +00:00
parent 80fb4a74a8
commit 85cb6c1287
6 changed files with 104 additions and 12 deletions

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

@ -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

@ -119,7 +119,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]);
@ -171,7 +173,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;
@ -290,11 +291,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 }) => {
@ -521,6 +517,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
styles.refreshIndicatorBackground,
{ backgroundColor: wallet ? WalletGradient.headerColorFor(wallet.type) : colors.background },
]}
testID="TransactionsListView"
/>
<FlatList<Transaction>
@ -529,7 +526,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
onEndReachedThreshold={0.3}
onEndReached={loadMoreTransactions}
ListFooterComponent={renderListFooterComponent}
data={listData}
data={getTransactions(limit)}
extraData={wallet}
keyExtractor={_keyExtractor}
renderItem={renderItem}
@ -544,7 +541,7 @@ const WalletTransactions: React.FC<WalletTransactionsProps> = ({ route }) => {
ListHeaderComponent={ListHeaderComponent}
ListEmptyComponent={
<ScrollView style={[styles.flex, { backgroundColor: colors.background }]} contentContainerStyle={styles.scrollViewContent}>
<Text numberOfLines={0} style={styles.emptyTxs}>
<Text numberOfLines={0} style={styles.emptyTxs} testID="TransactionsListEmpty">
{(isLightning() && loc.wallets.list_empty_txs1_lightning) || loc.wallets.list_empty_txs1}
</Text>
{isLightning() && <Text style={styles.emptyTxsLightning}>{loc.wallets.list_empty_txs2_lightning}</Text>}

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;
}