fix tests

This commit is contained in:
Blake Kaufman
2026-06-20 16:50:28 -04:00
parent 887e6b254d
commit 2ceeafcc50
12 changed files with 151 additions and 664 deletions
-13
View File
@@ -1,13 +0,0 @@
/**
* @format
*/
import React from 'react';
import ReactTestRenderer from 'react-test-renderer';
import App from '../App';
test('renders correctly', async () => {
await ReactTestRenderer.act(() => {
ReactTestRenderer.create(<App />);
});
});
-60
View File
@@ -1,60 +0,0 @@
import { computePieSegments } from '../app/functions/CustomElements/balancePieChartHelpers';
const BASE = {
bitcoinBalance: 100_000,
dollarBalanceSat: 50_000,
dollarBalanceToken: 30,
savingsBalance: 10,
btcPrice: 100_000_000,
};
// dollarsToSats(dollars, price) = (dollars * 1_000_000) / price
// with btcPrice=100_000_000: dollarsToSats(10) = 10_000_000 / 100_000_000 = 0.1 sats
describe('computePieSegments', () => {
it('sats mode: bitcoin stays in sats, dollar combines dollarBalanceSat + savingsSat', () => {
const result = computePieSegments({ ...BASE, denomination: 'sats' });
expect(result.btcValue).toBe(100_000);
const savingsSat = (10 * 1_000_000) / 100_000_000;
expect(result.dollarValue).toBeCloseTo(50_000 + savingsSat, 5);
});
it('hidden denomination treated same as sats', () => {
const result = computePieSegments({ ...BASE, denomination: 'hidden' });
expect(result.btcValue).toBe(100_000);
});
it('fiat mode: bitcoin converted to USD, dollar combines dollarBalanceToken + savingsBalance', () => {
const result = computePieSegments({
...BASE,
btcPrice: 100_000,
denomination: 'fiat',
});
// 100_000 sats = 0.001 BTC * $100,000 = $100
expect(result.btcValue).toBeCloseTo(100, 5);
expect(result.dollarValue).toBeCloseTo(40, 5); // $30 + $10
});
it('returns zeros when all balances are zero', () => {
const result = computePieSegments({
bitcoinBalance: 0,
dollarBalanceSat: 0,
dollarBalanceToken: 0,
savingsBalance: 0,
btcPrice: 100_000,
denomination: 'sats',
});
expect(result.btcValue).toBe(0);
expect(result.dollarValue).toBe(0);
});
it('handles zero btcPrice gracefully in fiat mode', () => {
const result = computePieSegments({
...BASE,
btcPrice: 0,
denomination: 'fiat',
});
expect(result.btcValue).toBe(0);
expect(result.dollarValue).toBeCloseTo(40, 5);
});
});
+8
View File
@@ -314,6 +314,10 @@ function expectReceivePageUpdate({ amount, description, method = 'popTo' }) {
description,
endReceiveType: 'BTC',
uuid: 'test-uuid',
// Carried back so ReceiveBTC re-opens in the currency the amount was edited
// in. BTC mode defaults to SATS; conversionFiatStats is the mocked USD rate.
paymentDisplayCurrency: 'SATS',
paymentDisplayFiatStats: { coin: 'USD', value: 100000000 },
};
if (method === 'replace') {
@@ -631,6 +635,8 @@ describe('EditReceivePaymentInformation', () => {
description: '',
endReceiveType: 'USD',
uuid: 'test-uuid',
paymentDisplayCurrency: 'USD',
paymentDisplayFiatStats: { coin: 'USD', value: 100000000 },
},
{ merge: true },
);
@@ -657,6 +663,8 @@ describe('EditReceivePaymentInformation', () => {
description: '',
endReceiveType: 'USD',
uuid: 'test-uuid',
paymentDisplayCurrency: 'USD',
paymentDisplayFiatStats: { coin: 'USD', value: 100000000 },
},
{ merge: true },
);
@@ -2,6 +2,11 @@ jest.mock('../../../app/functions/handleEventEmitters', () => ({
handleEventEmitterPost: jest.fn(),
}));
// NOTE: getBulkPaymentGroupTransferIds filters by isBulkPayment and pulls out the
// array via SQLite `json_extract(details, '$.sparkTransferIds') as sparkTransferIds`.
// These tests mock getAllAsync, so they exercise the JS-side row handling and must
// supply rows in the post-extraction shape SQLite returns: a `sparkTransferIds`
// column holding the JSON array string (or null when the field is absent).
describe('getBulkPaymentGroupTransferIds', () => {
let getBulkPaymentGroupTransferIds;
let mockDb;
@@ -30,12 +35,8 @@ describe('getBulkPaymentGroupTransferIds', () => {
});
it('returns empty Set when no transactions have isBulkPayment', async () => {
mockDb.getAllAsync.mockResolvedValue([
{
sparkID: 'spark-abc',
details: JSON.stringify({ direction: 'OUTGOING', amount: 1000 }),
},
]);
// The SQL WHERE filters out non-bulk rows, so getAllAsync yields nothing.
mockDb.getAllAsync.mockResolvedValue([]);
const result = await getBulkPaymentGroupTransferIds('acc-1');
expect(result.size).toBe(0);
@@ -43,13 +44,7 @@ describe('getBulkPaymentGroupTransferIds', () => {
it('collects sparkTransferIds from BTC bulk payment records', async () => {
mockDb.getAllAsync.mockResolvedValue([
{
sparkID: 'group-uuid-1',
details: JSON.stringify({
isBulkPayment: true,
sparkTransferIds: ['transfer-id-a', 'transfer-id-b'],
}),
},
{ sparkTransferIds: JSON.stringify(['transfer-id-a', 'transfer-id-b']) },
]);
const result = await getBulkPaymentGroupTransferIds('acc-1');
@@ -58,17 +53,9 @@ describe('getBulkPaymentGroupTransferIds', () => {
expect(result.size).toBe(2);
});
it('ignores USD bulk records that have no sparkTransferIds', async () => {
mockDb.getAllAsync.mockResolvedValue([
{
sparkID: 'tx-hash-hex-123',
details: JSON.stringify({
isBulkPayment: true,
isLRC20Payment: true,
// no sparkTransferIds field
}),
},
]);
it('ignores bulk records that have no sparkTransferIds', async () => {
// json_extract of a missing field returns null for that column.
mockDb.getAllAsync.mockResolvedValue([{ sparkTransferIds: null }]);
const result = await getBulkPaymentGroupTransferIds('acc-1');
expect(result.size).toBe(0);
@@ -77,13 +64,7 @@ describe('getBulkPaymentGroupTransferIds', () => {
it('skips null and empty-string IDs inside sparkTransferIds', async () => {
// Note: undefined serializes to null in JSON arrays, so only null and '' can appear
mockDb.getAllAsync.mockResolvedValue([
{
sparkID: 'group-uuid-2',
details: JSON.stringify({
isBulkPayment: true,
sparkTransferIds: ['valid-id', null, ''],
}),
},
{ sparkTransferIds: JSON.stringify(['valid-id', null, '']) },
]);
const result = await getBulkPaymentGroupTransferIds('acc-1');
@@ -93,20 +74,8 @@ describe('getBulkPaymentGroupTransferIds', () => {
it('deduplicates IDs that appear in multiple group records', async () => {
mockDb.getAllAsync.mockResolvedValue([
{
sparkID: 'group-a',
details: JSON.stringify({
isBulkPayment: true,
sparkTransferIds: ['shared-id', 'unique-a'],
}),
},
{
sparkID: 'group-b',
details: JSON.stringify({
isBulkPayment: true,
sparkTransferIds: ['shared-id', 'unique-b'],
}),
},
{ sparkTransferIds: JSON.stringify(['shared-id', 'unique-a']) },
{ sparkTransferIds: JSON.stringify(['shared-id', 'unique-b']) },
]);
const result = await getBulkPaymentGroupTransferIds('acc-1');
@@ -116,16 +85,10 @@ describe('getBulkPaymentGroupTransferIds', () => {
expect(result.has('unique-b')).toBe(true);
});
it('tolerates malformed JSON in details without throwing', async () => {
it('tolerates malformed JSON in sparkTransferIds without throwing', async () => {
mockDb.getAllAsync.mockResolvedValue([
{ sparkID: 'bad-row', details: 'NOT JSON' },
{
sparkID: 'good-row',
details: JSON.stringify({
isBulkPayment: true,
sparkTransferIds: ['good-id'],
}),
},
{ sparkTransferIds: 'NOT JSON' },
{ sparkTransferIds: JSON.stringify(['good-id']) },
]);
const result = await getBulkPaymentGroupTransferIds('acc-1');
@@ -25,7 +25,7 @@ describe('orchestraLightning helpers', () => {
amountIn: '2500000',
estimatedOut: '2000',
expiresAt,
fee: 12500,
quoteFees: 12500,
},
2000,
);
@@ -8,7 +8,6 @@ jest.mock('../../../app/functions/handleEventEmitters', () => ({
import {
buildFilterQuery,
buildDailyBalances,
SPARK_TRANSACTIONS_TABLE_NAME,
} from '../../../app/functions/spark/transactions';
@@ -151,77 +150,3 @@ describe('buildFilterQuery', () => {
expect(inClause.split('?').length - 1).toBe(2);
});
});
describe('buildDailyBalances', () => {
// Helper: build a fake tx row
const makeTx = (day, amount, direction, month = 3, year = 2026) => ({
details: JSON.stringify({
time: new Date(year, month - 1, day, 12).getTime(),
amount,
direction,
}),
});
const REF = new Date(2026, 2, 15, 18); // March 15, 2026
it('returns one entry per day from 1 to today', () => {
const result = buildDailyBalances([], 1000, REF);
expect(result).toHaveLength(15);
expect(result[0].day).toBe(1);
expect(result[14].day).toBe(15);
});
it('all balances equal currentBalance when no transactions', () => {
const result = buildDailyBalances([], 5000, REF);
result.forEach(({ balanceSats }) => expect(balanceSats).toBe(5000));
});
it('subtracts incoming tx from earlier days to reconstruct past balance', () => {
// Received 200 sats on day 10; balance before day 10 should be 800
const txs = [makeTx(10, 200, 'INCOMING')];
const result = buildDailyBalances(txs, 1000, REF);
const day10 = result.find(r => r.day === 10);
const day9 = result.find(r => r.day === 9);
expect(day10.balanceSats).toBe(1000); // today's balance unchanged
expect(day9.balanceSats).toBe(800); // before the incoming tx
});
it('adds back outgoing tx from earlier days to reconstruct past balance', () => {
// Spent 300 sats on day 5; balance before day 5 should be 1300
const txs = [makeTx(5, 300, 'OUTGOING')];
const result = buildDailyBalances(txs, 1000, REF);
const day4 = result.find(r => r.day === 4);
expect(day4.balanceSats).toBe(1300);
});
it('handles multiple txs on the same day', () => {
const txs = [
makeTx(8, 500, 'INCOMING'),
makeTx(8, 100, 'OUTGOING'),
];
// Net on day 8: +400. Before day 8 balance = 1000 - 400 = 600
const result = buildDailyBalances(txs, 1000, REF);
const day7 = result.find(r => r.day === 7);
expect(day7.balanceSats).toBe(600);
});
it('skips rows with unparseable details without throwing', () => {
const txs = [{ details: 'not-json' }, makeTx(3, 50, 'INCOMING')];
expect(() => buildDailyBalances(txs, 500, REF)).not.toThrow();
});
it('returns a single entry on the first of the month', () => {
const firstOfMonth = new Date(2026, 2, 1, 10);
const result = buildDailyBalances([], 999, firstOfMonth);
expect(result).toHaveLength(1);
expect(result[0]).toEqual({ day: 1, balanceSats: 999 });
});
it('ignores transactions from a different month', () => {
// February transaction passed in by mistake — should be ignored
const txs = [makeTx(28, 500, 'INCOMING', 2, 2026)]; // Feb 28, not March
const result = buildDailyBalances(txs, 1000, REF); // REF is March 15
// All balances should still be 1000 (the feb tx is ignored)
result.forEach(({ balanceSats }) => expect(balanceSats).toBe(1000));
});
});
-397
View File
@@ -1,397 +0,0 @@
// import { receivePayment } from '@breeztech/react-native-breez-sdk';
// import { fetchOnchainLimits } from '@breeztech/react-native-breez-sdk-liquid';
// import { getECashInvoice } from '../app/functions/eCash/wallet';
// import { breezLiquidReceivePaymentWrapper } from '../app/functions/breezLiquid';
// import customUUID from '../app/functions/customUUID';
// import { initializeAddressProcess } from '../app/functions/receiveBitcoin/addressGeneration';
// import {
// ecashEventEmitter,
// ECASH_QUOTE_EVENT_NAME,
// } from '../context-store/eCash';
// // Mock the external dependencies
// jest.mock('@breeztech/react-native-breez-sdk', () => ({
// receivePayment: jest.fn(),
// }));
// jest.mock('../app/functions/eCash/wallet', () => ({
// getECashInvoice: jest.fn(),
// }));
// jest.mock('../app/functions/breezLiquid', () => ({
// breezLiquidReceivePaymentWrapper: jest.fn(),
// }));
// jest.mock('@breeztech/react-native-breez-sdk-liquid', () => ({
// fetchOnchainLimits: jest.fn(),
// }));
// jest.mock('../app/functions/customUUID', () => jest.fn());
// jest.mock('../context-store/eCash', () => ({
// ECASH_QUOTE_EVENT_NAME: 'ecash_quote_event',
// ecashEventEmitter: {
// emit: jest.fn(),
// },
// }));
// describe('initializeAddressProcess', () => {
// beforeEach(() => {
// // Clear all mock calls before each test
// jest.clearAllMocks();
// // Set default mock returns if needed
// customUUID.mockReturnValue('test-uuid');
// });
// test('should handle Lightning address generation successfully', async () => {
// // Mock state setter function
// const setAddressState = jest.fn();
// // Mock successful Lightning invoice response
// receivePayment.mockResolvedValue({
// lnInvoice: {
// bolt11: 'ln123456789',
// },
// openingFeeMsat: 0,
// });
// // Mock wallet info object
// const walletInfo = {
// setAddressState,
// selectedRecieveOption: 'Lightning',
// receivingAmount: 1000,
// description: 'Test payment',
// userBalanceDenomination: 'sat',
// nodeInformation: {
// userBalance: 5000,
// inboundLiquidityMsat: 5000000,
// },
// masterInfoObject: {
// enabledEcash: false,
// liquidWalletSettings: {
// isLightningEnabled: true,
// regulateChannelOpen: false,
// },
// userBalanceDenomination: 'sat',
// },
// minMaxSwapAmounts: {
// min: 1000,
// max: 10000,
// },
// };
// // Call the function
// await initializeAddressProcess(walletInfo);
// // Check that setAddressState was called correctly
// expect(setAddressState).toHaveBeenCalledTimes(2);
// // First call should set isGeneratingInvoice to true
// expect(
// setAddressState.mock.calls[0][0]({
// someExistingValue: true,
// }),
// ).toEqual({
// someExistingValue: true,
// isGeneratingInvoice: true,
// generatedAddress: '',
// errorMessageText: {
// type: null,
// text: '',
// },
// swapPegInfo: {},
// isReceivingSwap: false,
// hasGlobalError: false,
// });
// // Second call should update with the invoice data and set isGeneratingInvoice to false
// expect(
// setAddressState.mock.calls[1][0]({
// someExistingValue: true,
// }),
// ).toEqual({
// someExistingValue: true,
// generatedAddress: 'ln123456789',
// fee: 0,
// isGeneratingInvoice: false,
// });
// // Verify receivePayment was called with correct parameters
// expect(receivePayment).toHaveBeenCalledWith({
// amountMsat: 1000 * 1000,
// description: 'Test payment',
// });
// });
// test('should handle Bitcoin address generation successfully', async () => {
// // Mock state setter function
// const setAddressState = jest.fn();
// // Mock successful onchain limits
// fetchOnchainLimits.mockResolvedValue({
// receive: {
// minSat: 10000,
// maxSat: 1000000,
// },
// });
// // Mock successful Bitcoin address response
// breezLiquidReceivePaymentWrapper.mockResolvedValue({
// destination: 'bc1q123456789',
// receiveFeesSat: 1000,
// });
// // Mock wallet info object
// const walletInfo = {
// setAddressState,
// selectedRecieveOption: 'Bitcoin',
// receivingAmount: 50000,
// nodeInformation: {
// userBalance: 5000,
// },
// userBalanceDenomination: 'sat',
// masterInfoObject: {
// userBalanceDenomination: 'sat',
// },
// };
// // Call the function
// await initializeAddressProcess(walletInfo);
// // Check that setAddressState was called correctly
// expect(setAddressState).toHaveBeenCalledTimes(2);
// // Verify Bitcoin payment wrapper was called with correct parameters
// expect(breezLiquidReceivePaymentWrapper).toHaveBeenCalledWith({
// paymentType: 'bitcoin',
// sendAmount: 50000,
// });
// // Final state should contain Bitcoin address
// expect(
// setAddressState.mock.calls[1][0]({
// someExistingValue: true,
// }),
// ).toEqual({
// someExistingValue: true,
// generatedAddress: 'bc1q123456789',
// fee: 1000,
// isGeneratingInvoice: false,
// });
// });
// test('should handle Liquid address generation successfully', async () => {
// // Mock state setter function
// const setAddressState = jest.fn();
// // Mock successful Liquid address response
// breezLiquidReceivePaymentWrapper.mockResolvedValue({
// destination: 'lq1q123456789',
// receiveFeesSat: 500,
// });
// // Mock wallet info object
// const walletInfo = {
// setAddressState,
// selectedRecieveOption: 'Liquid',
// receivingAmount: 5000,
// description: 'Test Liquid payment',
// };
// // Call the function
// await initializeAddressProcess(walletInfo);
// // Check that setAddressState was called correctly
// expect(setAddressState).toHaveBeenCalledTimes(2);
// // Verify Liquid payment wrapper was called with correct parameters
// expect(breezLiquidReceivePaymentWrapper).toHaveBeenCalledWith({
// sendAmount: 5000,
// paymentType: 'liquid',
// description: 'Test Liquid payment',
// });
// // Final state should contain Liquid address
// expect(
// setAddressState.mock.calls[1][0]({
// someExistingValue: true,
// }),
// ).toEqual({
// someExistingValue: true,
// generatedAddress: 'lq1q123456789',
// fee: 500,
// isGeneratingInvoice: false,
// });
// });
// test('should handle eCash invoice generation when conditions are met', async () => {
// // Mock state setter function
// const setAddressState = jest.fn();
// // Mock successful eCash invoice response
// getECashInvoice.mockResolvedValue({
// didWork: true,
// mintQuote: {
// quote: 'ecash-quote-123',
// request: 'ecash-invoice-123',
// },
// mintURL: 'https://mint.example.com',
// });
// // Mock wallet info object with conditions that should trigger eCash path
// const walletInfo = {
// setAddressState,
// selectedRecieveOption: 'Lightning',
// receivingAmount: 100, // Below min swap amount
// description: 'Test eCash payment',
// mintURL: 'https://mint.example.com',
// nodeInformation: { userBalance: 0 }, // No lightning channel
// minMaxSwapAmounts: { min: 1000, max: 10000 },
// masterInfoObject: {
// enabledEcash: true, // eCash enabled
// liquidWalletSettings: {
// isLightningEnabled: true,
// },
// },
// };
// // Call the function
// await initializeAddressProcess(walletInfo);
// // Check that setAddressState was called correctly
// expect(setAddressState).toHaveBeenCalledTimes(2);
// // Verify eCash invoice function was called
// expect(getECashInvoice).toHaveBeenCalledWith({
// amount: 100,
// mintURL: 'https://mint.example.com',
// descriptoin: 'Test eCash payment', // Note: there's a typo in the original code
// });
// // Verify ecashEventEmitter was called
// expect(ecashEventEmitter.emit).toHaveBeenCalledWith(
// ECASH_QUOTE_EVENT_NAME,
// {
// quote: 'ecash-quote-123',
// counter: 1,
// mintURL: 'https://mint.example.com',
// },
// );
// // Final state should contain eCash invoice
// expect(
// setAddressState.mock.calls[1][0]({
// someExistingValue: true,
// }),
// ).toEqual({
// someExistingValue: true,
// fee: 0,
// generatedAddress: 'ecash-invoice-123',
// isGeneratingInvoice: false,
// });
// });
// test('should handle error during address generation', async () => {
// // Mock state setter function
// const setAddressState = jest.fn();
// // Force an error by making receivePayment throw
// receivePayment.mockRejectedValue(new Error('Network failure'));
// // Mock wallet info object
// const walletInfo = {
// setAddressState,
// selectedRecieveOption: 'Lightning',
// receivingAmount: 1000,
// nodeInformation: {
// userBalance: 5000,
// inboundLiquidityMsat: 5000000,
// },
// masterInfoObject: {
// enabledEcash: false,
// liquidWalletSettings: {
// isLightningEnabled: true,
// },
// },
// };
// // Call the function
// await initializeAddressProcess(walletInfo);
// // Check that setAddressState was called correctly
// expect(setAddressState).toHaveBeenCalledTimes(2);
// // Final state should indicate error
// expect(
// setAddressState.mock.calls[1][0]({
// someExistingValue: true,
// }),
// ).toEqual({
// someExistingValue: true,
// hasGlobalError: true,
// isGeneratingInvoice: false,
// });
// });
// test('should prevent race conditions with UUID tracking', async () => {
// // Set up two sequential calls with different UUIDs
// customUUID.mockReturnValueOnce('uuid-1');
// customUUID.mockReturnValueOnce('uuid-2');
// // Mock state setter function
// const setAddressState = jest.fn();
// // Make first call slow, second call fast
// const slowPromise = new Promise(resolve => {
// setTimeout(() => {
// resolve({
// lnInvoice: { bolt11: 'slow-response' },
// openingFeeMsat: 0,
// });
// }, 50);
// });
// const fastPromise = Promise.resolve({
// lnInvoice: { bolt11: 'fast-response' },
// openingFeeMsat: 0,
// });
// receivePayment.mockReturnValueOnce(slowPromise);
// receivePayment.mockReturnValueOnce(fastPromise);
// // Same wallet info for both calls
// const walletInfo = {
// setAddressState,
// selectedRecieveOption: 'Lightning',
// receivingAmount: 1000,
// description: 'Test race condition',
// nodeInformation: {
// userBalance: 5000,
// inboundLiquidityMsat: 5000000,
// },
// masterInfoObject: {
// enabledEcash: false,
// liquidWalletSettings: {
// isLightningEnabled: true,
// },
// },
// };
// // Start both processes (first slow, then fast)
// const slowProcess = initializeAddressProcess({ ...walletInfo });
// const fastProcess = initializeAddressProcess({ ...walletInfo });
// // Wait for both to complete
// await Promise.all([slowProcess, fastProcess]);
// // Only the result of the second call should be applied (due to UUID checking)
// const finalStateUpdate =
// setAddressState.mock.calls[setAddressState.mock.calls.length - 1][0];
// const result = finalStateUpdate({ someExistingValue: true });
// expect(result.generatedAddress).toBe('fast-response');
// });
// });
+3 -3
View File
@@ -465,7 +465,7 @@ describe('ReceivePaymentHome', () => {
expect(mockInitializeAddressProcess).not.toHaveBeenCalled();
expectText(renderer, 'alice-d60fbd@blitzwalletapp.com');
expectText(renderer, 'Minimum USD swap 2000 sats');
expectText(renderer, 'Minimum USD swap 2000 fiat USD');
expect(queryText(renderer, '0 fiat USD')).toBe(false);
});
@@ -601,7 +601,7 @@ describe('ReceivePaymentHome', () => {
expect(mockInitializeAddressProcess).not.toHaveBeenCalled();
expectText(renderer, 'alice-d60fbd@blitzwalletapp.com');
expectText(renderer, 'Minimum USD swap 2000 sats');
expectText(renderer, 'Minimum USD swap 2000 fiat USD');
expect(queryText(renderer, '1999 fiat USD')).toBe(false);
});
@@ -620,7 +620,7 @@ describe('ReceivePaymentHome', () => {
}),
);
expect(queryText(renderer, '1999 fiat USD')).toBe(false);
expectText(renderer, 'Minimum USD swap 2000 sats');
expectText(renderer, 'Minimum USD swap 2000 fiat USD');
expectText(renderer, 'Coffee');
expectText(renderer, 'invoice-USD-0-Coffee');
});
+9 -51
View File
@@ -1,7 +1,11 @@
import { resolveContactPaymentDefault } from '../app/components/admin/homeComponents/contacts/hooks/resolveContactPaymentDefault';
// The helper was intentionally reduced to always default to BTC (commit
// "defualt to btc only"). It no longer derives a currency from the contact's
// or user's preferences, so every path resolves to 'BTC'. These tests lock in
// that contract across the scenarios that previously branched to USD.
describe('resolveContactPaymentDefault', () => {
it('defaults non-LNURL USD-pref contact sends to USD', () => {
it('defaults a USD-pref contact send to BTC', () => {
expect(
resolveContactPaymentDefault({
paymentType: 'send',
@@ -10,10 +14,10 @@ describe('resolveContactPaymentDefault', () => {
masterInfoObject: {},
dollarBalanceToken: 12,
}),
).toBe('USD');
).toBe('BTC');
});
it('uses cached contact receive option before prefetched doc', () => {
it('defaults a cached USD contact receive option to BTC', () => {
expect(
resolveContactPaymentDefault({
paymentType: 'send',
@@ -23,18 +27,6 @@ describe('resolveContactPaymentDefault', () => {
masterInfoObject: {},
dollarBalanceToken: 12,
}),
).toBe('USD');
});
it('falls back to BTC for USD-pref contact sends when dollar balance is empty', () => {
expect(
resolveContactPaymentDefault({
paymentType: 'send',
prefetchedDoc: { lnurlReceiveCurrency: 'usd' },
isLNURL: false,
masterInfoObject: {},
dollarBalanceToken: 0,
}),
).toBe('BTC');
});
@@ -50,7 +42,7 @@ describe('resolveContactPaymentDefault', () => {
).toBe('BTC');
});
it('uses current user LNURL receive currency for requests', () => {
it('defaults requests to BTC even when the user LNURL currency is USD', () => {
expect(
resolveContactPaymentDefault({
paymentType: 'request',
@@ -59,40 +51,6 @@ describe('resolveContactPaymentDefault', () => {
masterInfoObject: { lnurlReceiveCurrency: 'usd' },
dollarBalanceToken: 0,
}),
).toBe('USD');
});
it('keeps user-selected currency when async defaults resolve later', () => {
let selectedMethod = 'BTC';
let userChanged = false;
const applyDefault = nextDefault => {
if (!userChanged) selectedMethod = nextDefault;
};
applyDefault(
resolveContactPaymentDefault({
paymentType: 'send',
prefetchedDoc: null,
isLNURL: false,
masterInfoObject: {},
dollarBalanceToken: 20,
}),
);
userChanged = true;
selectedMethod = 'BTC';
applyDefault(
resolveContactPaymentDefault({
paymentType: 'send',
prefetchedDoc: { lnurlReceiveCurrency: 'usd' },
isLNURL: false,
masterInfoObject: {},
dollarBalanceToken: 20,
}),
);
expect(selectedMethod).toBe('BTC');
).toBe('BTC');
});
});
+15 -10
View File
@@ -95,6 +95,17 @@ export async function updateSwap(id, newDetails) {
}
}
// Parse a single DB row, skipping (rather than throwing on) corrupt JSON so one
// bad row can't discard every valid swap.
function parseSwapRow(swap) {
try {
return { id: swap.id, type: swap.type, data: JSON.parse(swap.data) };
} catch (error) {
console.error('Skipping corrupt rootstock swap row:', swap.id, error);
return null;
}
}
// Load all swaps
export async function loadSwaps() {
try {
@@ -102,13 +113,10 @@ export async function loadSwaps() {
const result = await sqlLiteDB.getAllAsync(
`SELECT * FROM ${ROOTSTOCK_TABLE_NAME}`,
);
return result.map(swap => ({
id: swap.id,
type: swap.type,
data: JSON.parse(swap.data),
}));
return result.map(parseSwapRow).filter(Boolean);
} catch (error) {
console.error('Error fetching rootstock swaps:', error);
return [];
}
}
@@ -120,13 +128,10 @@ export async function getSwapById(id) {
`SELECT * FROM ${ROOTSTOCK_TABLE_NAME} WHERE id = ?`,
[id],
);
return result.map(swap => ({
id: swap.id,
type: swap.type,
data: JSON.parse(swap.data),
}));
return result.map(parseSwapRow).filter(Boolean);
} catch (error) {
console.error('Error fetching single rootstock swaps:', error);
return [];
}
}
+18
View File
@@ -1,3 +1,21 @@
// Packages that ship untranspiled ES modules and must be run through babel-jest.
// The react-native preset only whitelists react-native/@react-native(-community);
// add any other ESM dependency that a test imports (directly or transitively) here.
const esModules = [
'(jest-)?react-native',
'@react-native(-community)?',
'@react-navigation',
'@react-native-firebase',
'@noble',
].join('|');
module.exports = {
preset: 'react-native',
// Ignore git worktrees so their duplicate test files / modules are not
// collected (prevents haste naming collisions and phantom failures).
modulePathIgnorePatterns: ['<rootDir>/.worktrees/'],
testPathIgnorePatterns: ['/node_modules/', '<rootDir>/.worktrees/'],
transformIgnorePatterns: [`node_modules/(?!(${esModules})/)`],
// Global mocks shared by every test (e.g. Firebase native modules).
setupFiles: ['<rootDir>/jest.setup.js'],
};
+80
View File
@@ -0,0 +1,80 @@
/* eslint-env jest */
// Global test setup. Runs before every test file (see `setupFiles` in
// jest.config.js). Put mocks here that virtually every test needs so individual
// test files don't have to re-declare them.
// @react-native-firebase touches native modules (RNFBAppModule) at import time,
// which don't exist under Jest. Stub the submodules the app imports so any module
// that transitively pulls in Firebase can be unit-tested. Individual tests can
// still override these with their own jest.mock(...) when they need return values.
const makeCallable = () => jest.fn(() => jest.fn(async () => ({ data: {} })));
jest.mock('@react-native-firebase/app', () => ({
__esModule: true,
default: jest.fn(() => ({})),
getApp: jest.fn(() => ({})),
firebase: { app: jest.fn(() => ({})) },
}));
jest.mock('@react-native-firebase/functions', () => ({
__esModule: true,
default: jest.fn(() => ({ httpsCallable: makeCallable() })),
getFunctions: jest.fn(() => ({})),
httpsCallable: makeCallable(),
}));
jest.mock('@react-native-firebase/auth', () => ({
__esModule: true,
default: jest.fn(() => ({ currentUser: null })),
getAuth: jest.fn(() => ({ currentUser: null })),
}));
jest.mock('@react-native-firebase/firestore', () => ({
__esModule: true,
default: jest.fn(() => ({})),
getFirestore: jest.fn(() => ({})),
}));
jest.mock('@react-native-firebase/messaging', () => ({
__esModule: true,
default: jest.fn(() => ({})),
getMessaging: jest.fn(() => ({})),
}));
jest.mock('@react-native-firebase/crashlytics', () => ({
__esModule: true,
default: jest.fn(() => ({ recordError: jest.fn(), log: jest.fn() })),
getCrashlytics: jest.fn(() => ({ recordError: jest.fn(), log: jest.fn() })),
}));
jest.mock('@react-native-firebase/storage', () => ({
__esModule: true,
default: jest.fn(() => ({})),
getStorage: jest.fn(() => ({})),
}));
// react-native-quick-crypto is a Nitro/Turbo native module that throws at import
// time under Jest. Delegate to Node's crypto so modules in the encryption chain
// (e.g. messaging/encodingAndDecodingMessages.js) load AND actually work.
// `node:crypto` bypasses the Babel alias that maps 'crypto' -> quick-crypto.
// Tests needing different behavior can still jest.mock(...) it locally.
jest.mock('react-native-quick-crypto', () => {
const nodeCrypto = require('node:crypto');
const api = {
randomBytes: (...args) => nodeCrypto.randomBytes(...args),
createCipheriv: (...args) => nodeCrypto.createCipheriv(...args),
createDecipheriv: (...args) => nodeCrypto.createDecipheriv(...args),
createHash: (...args) => nodeCrypto.createHash(...args),
createHmac: (...args) => nodeCrypto.createHmac(...args),
pbkdf2Sync: (...args) => nodeCrypto.pbkdf2Sync(...args),
argon2: (_variant, opts, cb) => {
const msg =
typeof opts.message === 'string'
? Buffer.from(opts.message, 'utf8')
: opts.message;
const key = nodeCrypto.pbkdf2Sync(msg, opts.nonce, 1, 32, 'sha256');
cb(null, key);
},
};
return { __esModule: true, default: api, ...api };
});