ADD: arkade ln pushes (#8634)
Some checks are pending
Build Release and Upload to TestFlight (iOS) / build (push) Waiting to run
Build Release and Upload to TestFlight (iOS) / testflight-upload (push) Blocked by required conditions
BuildReleaseApk / buildReleaseApk (push) Waiting to run
BuildReleaseApk / browserstack (push) Blocked by required conditions
Some checks are pending
Build Release and Upload to TestFlight (iOS) / build (push) Waiting to run
Build Release and Upload to TestFlight (iOS) / testflight-upload (push) Blocked by required conditions
BuildReleaseApk / buildReleaseApk (push) Waiting to run
BuildReleaseApk / browserstack (push) Blocked by required conditions
This commit is contained in:
parent
f334b985e8
commit
0181f0a849
2
.github/workflows/ci.yml
vendored
2
.github/workflows/ci.yml
vendored
@ -53,6 +53,7 @@ jobs:
|
||||
BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}}
|
||||
HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }}
|
||||
HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }}
|
||||
HD_MNEMONIC_OLD: ${{ secrets.HD_MNEMONIC_OLD }}
|
||||
HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }}
|
||||
HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }}
|
||||
HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }}
|
||||
@ -83,6 +84,7 @@ jobs:
|
||||
BIP47_HD_MNEMONIC: ${{ secrets.BIP47_HD_MNEMONIC}}
|
||||
HD_MNEMONIC: ${{ secrets.HD_MNEMONIC }}
|
||||
HD_MNEMONIC_BIP49: ${{ secrets.HD_MNEMONIC_BIP49 }}
|
||||
HD_MNEMONIC_OLD: ${{ secrets.HD_MNEMONIC_OLD }}
|
||||
HD_MNEMONIC_BIP49_MANY_TX: ${{ secrets.HD_MNEMONIC_BIP49_MANY_TX }}
|
||||
HD_MNEMONIC_BIP84: ${{ secrets.HD_MNEMONIC_BIP84 }}
|
||||
HD_MNEMONIC_BREAD: ${{ secrets.HD_MNEMONIC_BREAD }}
|
||||
|
||||
@ -2,4 +2,7 @@
|
||||
* Let's keep config vars, constants and definitions here
|
||||
*/
|
||||
|
||||
export const groundControlUri: string = 'https://groundcontrol.bluewallet.io/';
|
||||
export const groundControlUri: string = 'https://groundcontrol.bluewallet.io';
|
||||
|
||||
/** bitcoin-payment-push-service base URL, no trailing slash. Empty = disabled. */
|
||||
export const arkadePaymentPushUri: string = 'https://electrum2.bluewallet.io:444';
|
||||
|
||||
@ -8,8 +8,9 @@ import {
|
||||
Notifications,
|
||||
} from 'react-native-notifications';
|
||||
import { checkNotifications, requestNotifications, RESULTS } from 'react-native-permissions';
|
||||
import type { BoltzReverseSwap } from '@arkade-os/boltz-swap';
|
||||
import loc from '../loc';
|
||||
import { groundControlUri } from './constants';
|
||||
import { arkadePaymentPushUri, groundControlUri } from './constants';
|
||||
import { fetch } from '../util/fetch';
|
||||
|
||||
const PUSH_TOKEN = 'PUSH_TOKEN';
|
||||
@ -251,6 +252,29 @@ export const tryToObtainPermissions = async (): Promise<boolean> => {
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
||||
export const enqueueTestPushNotification = async (): Promise<void> => {
|
||||
const pushToken = await getPushToken();
|
||||
if (!pushToken?.token || !pushToken?.os) {
|
||||
throw new Error('No push token available');
|
||||
}
|
||||
|
||||
const response = await fetch(`${baseURI}/enqueue`, {
|
||||
method: 'POST',
|
||||
headers: _getHeaders(),
|
||||
body: JSON.stringify({
|
||||
type: 5,
|
||||
token: pushToken.token,
|
||||
os: pushToken.os,
|
||||
text: 'Test push notification',
|
||||
}),
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error(`Enqueue request failed with status ${response.status}: ${response.statusText}`);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Submits onchain bitcoin addresses and ln invoice preimage hashes to GroundControl server, so later we could
|
||||
* be notified if they were paid
|
||||
@ -326,6 +350,44 @@ export const majorTomToGroundControl = async (addresses: string[], hashes: strin
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Registers an Ark swap with the bitcoin-payment-push-service so the device is
|
||||
* pushed when the invoice gets paid. Fire-and-forget: never throws, gated by
|
||||
* the same opt-out/token rules as majorTomToGroundControl(). The swap's
|
||||
* preimage is always stripped before leaving the device.
|
||||
*/
|
||||
export const registerArkPaymentPush = async (paymentHash: string, label: string, pendingSwap: BoltzReverseSwap): Promise<void> => {
|
||||
if (!arkadePaymentPushUri) return;
|
||||
try {
|
||||
const noAndDontAskFlag = await AsyncStorage.getItem(NOTIFICATIONS_NO_AND_DONT_ASK_FLAG);
|
||||
if (noAndDontAskFlag === 'true') {
|
||||
console.warn('User has opted out of notifications.');
|
||||
return;
|
||||
}
|
||||
|
||||
const pushToken = await getPushToken();
|
||||
if (!pushToken || !pushToken.token || !pushToken.os) {
|
||||
return;
|
||||
}
|
||||
|
||||
const response = await fetch(`${arkadePaymentPushUri}/register`, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
topic: paymentHash,
|
||||
label,
|
||||
swap: { ...pendingSwap, preimage: '' },
|
||||
}),
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error(`status ${response.status}`);
|
||||
}
|
||||
console.log('[ARK] payment push registration ok');
|
||||
} catch (e: any) {
|
||||
console.log('[ARK] payment push registration failed:', e?.message ?? e);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns a permissions object:
|
||||
* alert: boolean
|
||||
|
||||
@ -29,6 +29,7 @@ import assert from 'assert';
|
||||
import ecc from '../../blue_modules/noble_ecc.ts';
|
||||
import { Measure } from '../measure.ts';
|
||||
import { deleteArkadeRealm, getArkadeRealm } from '../../blue_modules/arkade-adapters/realm/realmInstance';
|
||||
import { registerArkPaymentPush } from '../../blue_modules/notifications';
|
||||
const { bech32m } = require('bech32');
|
||||
|
||||
const bip32 = BIP32Factory(ecc);
|
||||
@ -710,6 +711,8 @@ export class LightningArkWallet extends LightningCustodianWallet {
|
||||
console.log('Pending swap', result.pendingSwap);
|
||||
console.log('Preimage', result.preimage);
|
||||
|
||||
registerArkPaymentPush(result.paymentHash, memo, result.pendingSwap); // fire-and-forget, never throws
|
||||
|
||||
return result.invoice;
|
||||
}
|
||||
|
||||
|
||||
@ -91,7 +91,7 @@
|
||||
"lint": " npm run tslint && node scripts/find-unused-loc.js && node scripts/find-english-leftovers.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",
|
||||
"lint:quickfix": "git status --porcelain | grep -v '\\.json' | grep -E '\\.js|\\.ts' --color=never | awk '{print $2}' | xargs eslint --fix; exit 0",
|
||||
"unit": "jest -b -w tests/unit/*"
|
||||
"unit": "jest -b tests/unit/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@arkade-os/boltz-swap": "0.3.37",
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import React, { useCallback, useEffect, useState } from 'react';
|
||||
import { Linking, StyleSheet, View, Pressable, AppState, Text } from 'react-native';
|
||||
import { StyleSheet, View, Pressable, AppState, Text } from 'react-native';
|
||||
import {
|
||||
getPushToken,
|
||||
getStoredNotifications,
|
||||
@ -9,9 +9,12 @@ import {
|
||||
cleanUserOptOutFlag,
|
||||
checkPermissions,
|
||||
checkNotificationPermissionStatus,
|
||||
enqueueTestPushNotification,
|
||||
NOTIFICATIONS_NO_AND_DONT_ASK_FLAG,
|
||||
} from '../../blue_modules/notifications';
|
||||
import presentAlert from '../../components/Alert';
|
||||
import { BlueSpacing20 } from '../../components/BlueSpacing';
|
||||
import { Button } from '../../components/Button';
|
||||
import CopyToClipboardButton from '../../components/CopyToClipboardButton';
|
||||
import { useTheme } from '../../components/themes';
|
||||
import loc from '../../loc';
|
||||
@ -164,6 +167,18 @@ const NotificationSettings: React.FC = () => {
|
||||
};
|
||||
}, []);
|
||||
|
||||
const enqueueTestPush = useCallback(async () => {
|
||||
setIsLoading(true);
|
||||
try {
|
||||
await enqueueTestPushNotification();
|
||||
} catch (error) {
|
||||
console.error('Error enqueueing test push:', error);
|
||||
presentAlert({ message: (error as Error).message });
|
||||
} finally {
|
||||
setIsLoading(false);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const renderDeveloperSettings = useCallback(() => {
|
||||
if (tapCount < 10) return null;
|
||||
|
||||
@ -171,15 +186,6 @@ const NotificationSettings: React.FC = () => {
|
||||
<View>
|
||||
<View style={[styles.divider, { backgroundColor: colors.lightBorder ?? colors.borderTopColor }]} />
|
||||
|
||||
<SettingsListItem
|
||||
title="github.com/BlueWallet/GroundControl"
|
||||
iconName="github"
|
||||
onPress={() => Linking.openURL('https://github.com/BlueWallet/GroundControl')}
|
||||
chevron
|
||||
position="single"
|
||||
spacingTop
|
||||
/>
|
||||
|
||||
<SettingsCard style={styles.card}>
|
||||
<View style={styles.cardContent}>
|
||||
<Text style={[styles.centered, { color: colors.foregroundColor }]} onPress={() => setTapCount(tapCount + 1)}>
|
||||
@ -192,11 +198,14 @@ const NotificationSettings: React.FC = () => {
|
||||
<View>
|
||||
<CopyToClipboardButton stringToCopy={tokenInfo} displayText={tokenInfo} />
|
||||
</View>
|
||||
|
||||
<BlueSpacing20 />
|
||||
<Button onPress={enqueueTestPush} title="Enqueue test push notification" disabled={isLoading} />
|
||||
</View>
|
||||
</SettingsCard>
|
||||
</View>
|
||||
);
|
||||
}, [tapCount, colors, tokenInfo]);
|
||||
}, [tapCount, colors, isLoading, tokenInfo, enqueueTestPush]);
|
||||
|
||||
const renderPushNotificationsExplanation = useCallback(() => {
|
||||
return (
|
||||
|
||||
@ -19,7 +19,7 @@
|
||||
*/
|
||||
|
||||
import { closeAllArkadeRealms, __testing__ as realmTesting } from '../../blue_modules/arkade-adapters/realm/realmInstance';
|
||||
import { __testing__ as walletTesting } from '../../class/wallets/lightning-ark-wallet';
|
||||
import { LightningArkWallet, __testing__ as walletTesting } from '../../class/wallets/lightning-ark-wallet';
|
||||
|
||||
const Realm = require('realm');
|
||||
|
||||
@ -81,3 +81,39 @@ export const arkadeMockState = {
|
||||
Keychain.__mockKeychainHelpers.store.set(service, { username: service, password, service });
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* Tear down a LightningArkWallet after integration tests. Stops SDK background
|
||||
* loops (ContractWatcher SSE, VtxoManager polling, SwapManager) via dispose()
|
||||
* before clearing module-private caches.
|
||||
*/
|
||||
export async function teardownArkadeWallet(w: LightningArkWallet): Promise<void> {
|
||||
try {
|
||||
await w.onDelete();
|
||||
} catch {
|
||||
// onDelete already logs and swallows per-namespace errors.
|
||||
}
|
||||
}
|
||||
|
||||
/** Best-effort dispose of any Arkade SDK runtime still cached module-wide. */
|
||||
export async function disposeAllArkadeRuntime(): Promise<void> {
|
||||
for (const ns of Object.keys(walletTesting.staticSwapsCache)) {
|
||||
const swaps = walletTesting.staticSwapsCache[ns];
|
||||
try {
|
||||
if (typeof swaps?.dispose === 'function') await swaps.dispose();
|
||||
} catch {}
|
||||
delete walletTesting.staticSwapsCache[ns];
|
||||
}
|
||||
for (const ns of Object.keys(walletTesting.staticWalletCache)) {
|
||||
const sdkWallet = walletTesting.staticWalletCache[ns];
|
||||
try {
|
||||
if (typeof sdkWallet?.dispose === 'function') await sdkWallet.dispose();
|
||||
} catch {}
|
||||
delete walletTesting.staticWalletCache[ns];
|
||||
}
|
||||
walletTesting.initInFlight.clear();
|
||||
walletTesting.restoreInFlight.clear();
|
||||
for (const k of Object.keys(walletTesting.boardingLock)) delete walletTesting.boardingLock[k];
|
||||
closeAllArkadeRealms();
|
||||
realmTesting.openInFlight.clear();
|
||||
}
|
||||
|
||||
@ -110,3 +110,25 @@ export function installSdkProviderSpies(): void {
|
||||
export function restoreSdkProviderSpies(): void {
|
||||
jest.restoreAllMocks();
|
||||
}
|
||||
|
||||
let backgroundLoopSpies: jest.SpiedFunction<any>[] = [];
|
||||
|
||||
export function restoreSdkBackgroundLoopStubs(): void {
|
||||
for (const spy of backgroundLoopSpies) spy.mockRestore();
|
||||
backgroundLoopSpies = [];
|
||||
}
|
||||
|
||||
/**
|
||||
* Stub only the SDK background subscriptions that Jest cannot shut down
|
||||
* cleanly (VtxoManager polling, SwapManager WebSocket, ContractWatcher SSE).
|
||||
* Real HTTP calls (getInfo, getTransactionHistory, restoreSwaps, etc.) still
|
||||
* run — use in env-gated integration tests that hit production services.
|
||||
*/
|
||||
export function installSdkBackgroundLoopStubs(): void {
|
||||
restoreSdkBackgroundLoopStubs();
|
||||
backgroundLoopSpies = [
|
||||
jest.spyOn(VtxoManager.prototype as any, 'initializeSubscription').mockResolvedValue(undefined),
|
||||
jest.spyOn(SwapManager.prototype as any, 'start').mockResolvedValue(undefined),
|
||||
jest.spyOn(ContractManager.prototype as any, 'initialize').mockResolvedValue(undefined),
|
||||
];
|
||||
}
|
||||
|
||||
@ -2,6 +2,8 @@ import assert from 'assert';
|
||||
|
||||
import { HDSegwitBech32Wallet } from '../../class/wallets/hd-segwit-bech32-wallet';
|
||||
import { LightningArkWallet } from '../../class/wallets/lightning-ark-wallet.ts';
|
||||
import { disposeAllArkadeRuntime, teardownArkadeWallet } from '../helpers/arkadeMocks';
|
||||
import { installSdkBackgroundLoopStubs, restoreSdkBackgroundLoopStubs } from '../helpers/sdkProviderMocks';
|
||||
|
||||
// Ark storage lives in Realm, not AsyncStorage. Realm + Keychain are mocked
|
||||
// globally by tests/setup.js (per-path Realm + service-keyed Keychain), and
|
||||
@ -15,29 +17,41 @@ jest.setTimeout(30_000);
|
||||
const w = new LightningArkWallet();
|
||||
|
||||
beforeAll(async () => {
|
||||
// Install before the env guard: `can generate` runs init() regardless of
|
||||
// HD_MNEMONIC_OLD, and without the stubs its background loops keep Jest alive.
|
||||
installSdkBackgroundLoopStubs();
|
||||
if (!process.env.HD_MNEMONIC_OLD) {
|
||||
console.error('process.env.HD_MNEMONIC_OLD not set, skipped');
|
||||
return;
|
||||
}
|
||||
w.setSecret('arkade://' + process.env.HD_MNEMONIC_OLD);
|
||||
await w.init();
|
||||
await w.restoreSwaps();
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await new Promise(resolve => setTimeout(resolve, 3_000)); // sleep
|
||||
if (process.env.HD_MNEMONIC_OLD) {
|
||||
await teardownArkadeWallet(w);
|
||||
}
|
||||
await disposeAllArkadeRuntime();
|
||||
restoreSdkBackgroundLoopStubs();
|
||||
});
|
||||
|
||||
describe('LightningArkWallet (integration)', () => {
|
||||
it('can generate', async () => {
|
||||
const wGenerated = new LightningArkWallet();
|
||||
await wGenerated.generate();
|
||||
try {
|
||||
await wGenerated.generate();
|
||||
|
||||
assert.ok(wGenerated.getSecret().startsWith('arkade://'));
|
||||
assert.ok(wGenerated.getSecret().startsWith('arkade://'));
|
||||
|
||||
const mnemonics = wGenerated.getSecret().replace('arkade://', '');
|
||||
const hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(mnemonics);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
const mnemonics = wGenerated.getSecret().replace('arkade://', '');
|
||||
const hd = new HDSegwitBech32Wallet();
|
||||
hd.setSecret(mnemonics);
|
||||
assert.ok(hd.validateMnemonic());
|
||||
} finally {
|
||||
await teardownArkadeWallet(wGenerated);
|
||||
}
|
||||
});
|
||||
|
||||
it('can fetch balance', async () => {
|
||||
@ -70,47 +84,48 @@ describe('LightningArkWallet (integration)', () => {
|
||||
}
|
||||
|
||||
await w.fetchTransactions();
|
||||
await w.fetchUserInvoices();
|
||||
|
||||
const txs = w.getTransactions();
|
||||
assert.ok(txs.length > 0);
|
||||
assert.ok(txs.length > 0, 'Should have transaction history from the Ark indexer');
|
||||
|
||||
// Find the reverse swap (incoming) transaction
|
||||
const receiveTx = txs.find(t => t.value! > 0);
|
||||
assert.ok(receiveTx, 'Should have at least one receive transaction');
|
||||
assert.strictEqual(receiveTx.memo, 'test invoice');
|
||||
assert.strictEqual(receiveTx.value, 9999);
|
||||
assert.strictEqual(receiveTx.timestamp, 1761224952);
|
||||
assert.strictEqual(receiveTx.ispaid, true);
|
||||
assert.ok(receiveTx.payment_hash);
|
||||
assert.ok(receiveTx.payment_request);
|
||||
assert.strictEqual(receiveTx.payment_preimage, '7244f7e956a91171038ea935d56cdb758cc36c345f0aa92764bfed6fe6fc9b17');
|
||||
assert.ok(receiveTx.value! > 0);
|
||||
assert.ok(receiveTx.timestamp! > 0);
|
||||
assert.ok(receiveTx.memo);
|
||||
|
||||
// Find the submarine swap (outgoing) transaction
|
||||
const sendTx = txs.find(t => t.value! < 0);
|
||||
assert.ok(sendTx, 'Should have at least one send transaction');
|
||||
assert.strictEqual(sendTx.value, -8001);
|
||||
assert.strictEqual(sendTx.timestamp, 1761225645);
|
||||
assert.strictEqual(sendTx.ispaid, true);
|
||||
assert.ok(sendTx.payment_hash);
|
||||
assert.ok(sendTx.payment_request);
|
||||
assert.strictEqual(sendTx.payment_preimage, '182fb8f273bda01b22c0e91991e093e18b2970f389fc7f7a2121870324eb2de5');
|
||||
const swapHistory: any[] = (w as any)._swapHistory ?? [];
|
||||
const settledReverse = swapHistory.find(s => s.type === 'reverse' && s.status === 'invoice.settled');
|
||||
if (settledReverse) {
|
||||
// When Boltz reverse-swap history is restored, settled receives are enriched in place.
|
||||
assert.strictEqual(receiveTx.ispaid, true);
|
||||
assert.ok(receiveTx.payment_hash);
|
||||
assert.ok(receiveTx.payment_request);
|
||||
assert.ok(receiveTx.payment_preimage);
|
||||
assert.notStrictEqual(receiveTx.memo, 'Received');
|
||||
|
||||
const ownInvoice = settledReverse.request?.invoice || settledReverse.response?.invoice;
|
||||
if (ownInvoice) {
|
||||
assert.ok(w.isInvoiceGeneratedByWallet(ownInvoice));
|
||||
}
|
||||
}
|
||||
|
||||
const settledSubmarine = swapHistory.find(s => s.type === 'submarine' && s.status === 'transaction.claimed');
|
||||
if (settledSubmarine) {
|
||||
const sendTx = txs.find(t => t.value! < 0);
|
||||
assert.ok(sendTx, 'Should have a send transaction when submarine swap history exists');
|
||||
assert.strictEqual(sendTx.ispaid, true);
|
||||
assert.ok(sendTx.payment_hash);
|
||||
assert.ok(sendTx.payment_request);
|
||||
assert.ok(sendTx.payment_preimage);
|
||||
}
|
||||
|
||||
const invoices = await w.getUserInvoices();
|
||||
assert.ok(invoices.length > 0);
|
||||
assert(invoices[0].value! > 0);
|
||||
assert(invoices[0].ispaid);
|
||||
|
||||
assert.ok(
|
||||
w.isInvoiceGeneratedByWallet(
|
||||
'lnbc100u1p50528cpp5rhy4fgs0ff23asecxtxt9zvc3apn0p8h7fxsj0d5k7j3x92zwhlqdq5w3jhxapqd9h8vmmfvdjscqrp80xqyf8ucsp5vcsrzye432n9wh0zwuv5z8y5n9zvkwpctr685e80utzc2yueccms9qxpqysgqd87swq3hput9k6llp0wxg098hc7ge3e5nrtnvak6zreywzaf4k9s8d3u4hrmt3m22kf0jt7ruqj0caknk5ykzdenjdphz50t7xrstnqqn6aw0m',
|
||||
),
|
||||
);
|
||||
assert.ok(
|
||||
!w.isInvoiceGeneratedByWallet(
|
||||
'lnbc80u1p5052hwpp5z4ln6hyq4wcck809pt7f0q54ag5he6ce797flm7gl9vuccm9lx2sdqqcqzysxqyz5vqsp5nh9fl4g36606tvxswtnfxzy55yze2656cw2fya7dhl8r6u0czyds9qxpqysgq83sw25g9d9ltr05nkfzejnvvunzkrk4qeuxhszuvvsguk5m6vmg3a7n5nd67l9frru3kjzpt8x6jfusjyc7ezh49jeeh900kt3v30qsqzq7fst',
|
||||
),
|
||||
);
|
||||
if (settledReverse) {
|
||||
assert.ok(invoices.length > 0);
|
||||
assert(invoices[0].value! > 0);
|
||||
assert(invoices[0].ispaid);
|
||||
}
|
||||
});
|
||||
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
|
||||
@ -7,7 +7,8 @@ console.warn = (...args) => {
|
||||
if (
|
||||
typeof args[0] === 'string' &&
|
||||
(args[0].startsWith('WARNING: Sending to a future segwit version address can lead to loss of funds') ||
|
||||
args[0].startsWith('only compressed public keys are good'))
|
||||
args[0].startsWith('only compressed public keys are good') ||
|
||||
args[0].startsWith('Using standard fetch instead of expo/fetch'))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
@ -510,6 +511,17 @@ jest.mock('../blue_modules/analytics', () => {
|
||||
return ret;
|
||||
});
|
||||
|
||||
// addInvoice() registers a fire-and-forget payment-push callback; disable the
|
||||
// URI in unit tests so node-fetch does not leave in-flight handles after the
|
||||
// suite exits (which makes Jest fail with "did not exit one second after").
|
||||
jest.mock('../blue_modules/constants', () => {
|
||||
const actual = jest.requireActual('../blue_modules/constants');
|
||||
return {
|
||||
...actual,
|
||||
arkadePaymentPushUri: '',
|
||||
};
|
||||
});
|
||||
|
||||
jest.mock('react-native-share', () => {
|
||||
return {
|
||||
open: jest.fn(),
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
import React from 'react';
|
||||
import { fireEvent, render, waitFor } from '@testing-library/react-native';
|
||||
|
||||
import { _setSkipUpdateExchangeRate } from '../../blue_modules/currency';
|
||||
import TransactionStatus from '../../screen/transactions/TransactionStatus';
|
||||
|
||||
// TransactionStatus renders fiat amounts via satoshiToLocalCurrency(), which
|
||||
// kicks off a real exchange-rate fetch when no rate is cached — leaving a TLS
|
||||
// socket open after the run ("Jest did not exit one second after...").
|
||||
_setSkipUpdateExchangeRate();
|
||||
|
||||
type MockStorage = {
|
||||
wallets: any[];
|
||||
txMetadata: Record<string, any>;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user