Skip to content

Commit 5dd9d93

Browse files
cursoragentn3ps
andcommitted
test: add transaction coverage cases
Co-authored-by: Francis Nepomuceno <n3ps@users.noreply.github.com>
1 parent 2095c21 commit 5dd9d93

2 files changed

Lines changed: 295 additions & 3 deletions

File tree

app/components/Views/UnifiedTransactionsView/helpers/transformations.test.ts

Lines changed: 165 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,12 @@ import type {
33
V4MultiAccountTransactionsResponse,
44
} from '@metamask/core-backend';
55
import type { InfiniteData } from '@tanstack/react-query';
6-
import { selectTransactions } from './transformations';
6+
import {
7+
isBridgeHistoryForEvmTransaction,
8+
mergeTransactionsByTime,
9+
selectTransactions,
10+
} from './transformations';
11+
import { TransactionKind } from '../types';
712

813
describe('selectTransactions', () => {
914
const address = '0x0000000000000000000000000000000000000001';
@@ -88,4 +93,163 @@ describe('selectTransactions', () => {
8893
expect(result.pages[0].data).toHaveLength(1);
8994
expect(result.pages[0].data[0].hash).toBe('0xnormal');
9095
});
96+
97+
it('filters incoming token transfers', () => {
98+
const incomingTokenTransfer = buildTransaction({
99+
hash: '0xincoming-token',
100+
from: otherAddress,
101+
to: address,
102+
valueTransfers: [
103+
{
104+
contractAddress: '0x00000000000000000000000000000000000000aa',
105+
from: otherAddress,
106+
to: address,
107+
},
108+
],
109+
} as Partial<V1TransactionByHashResponse>);
110+
const outgoing = buildTransaction({ hash: '0xoutgoing' });
111+
112+
const result = selectTransactions({ address })(
113+
buildData([incomingTokenTransfer, outgoing]),
114+
);
115+
116+
expect(result.pages[0].data).toHaveLength(1);
117+
expect(result.pages[0].data[0].hash).toBe('0xoutgoing');
118+
});
119+
120+
it('filters incoming native transfers', () => {
121+
const incomingNativeTransfer = buildTransaction({
122+
hash: '0xincoming-native',
123+
from: otherAddress,
124+
to: address,
125+
valueTransfers: [
126+
{
127+
from: otherAddress,
128+
to: address,
129+
},
130+
],
131+
} as Partial<V1TransactionByHashResponse>);
132+
const outgoing = buildTransaction({ hash: '0xoutgoing' });
133+
134+
const result = selectTransactions({ address })(
135+
buildData([incomingNativeTransfer, outgoing]),
136+
);
137+
138+
expect(result.pages[0].data).toHaveLength(1);
139+
expect(result.pages[0].data[0].hash).toBe('0xoutgoing');
140+
});
141+
142+
it('filters zero-value self sends without calldata or transfers', () => {
143+
const selfSend = buildTransaction({
144+
from: address,
145+
to: address,
146+
value: '0',
147+
methodId: '0x',
148+
valueTransfers: [],
149+
});
150+
151+
const result = selectTransactions({ address })(buildData([selfSend]));
152+
153+
expect(result.pages[0].data).toHaveLength(0);
154+
});
155+
});
156+
157+
describe('isBridgeHistoryForEvmTransaction', () => {
158+
it('matches bridge history by original transaction id', () => {
159+
const tx = {
160+
id: 'tx-id',
161+
actionId: 'action-id',
162+
};
163+
const bridgeHistoryValues = [
164+
{
165+
txMetaId: 'different-id',
166+
originalTransactionId: 'action-id',
167+
},
168+
];
169+
170+
const result = isBridgeHistoryForEvmTransaction(
171+
tx as Parameters<typeof isBridgeHistoryForEvmTransaction>[0],
172+
bridgeHistoryValues as Parameters<
173+
typeof isBridgeHistoryForEvmTransaction
174+
>[1],
175+
);
176+
177+
expect(result).toBe(true);
178+
});
179+
180+
it('matches bridge history by source hash', () => {
181+
const tx = {
182+
id: 'tx-id',
183+
hash: '0xABC',
184+
};
185+
const bridgeHistoryValues = [
186+
{
187+
txMetaId: 'different-id',
188+
status: {
189+
srcChain: {
190+
txHash: '0xabc',
191+
},
192+
},
193+
},
194+
];
195+
196+
const result = isBridgeHistoryForEvmTransaction(
197+
tx as Parameters<typeof isBridgeHistoryForEvmTransaction>[0],
198+
bridgeHistoryValues as Parameters<
199+
typeof isBridgeHistoryForEvmTransaction
200+
>[1],
201+
);
202+
203+
expect(result).toBe(true);
204+
});
205+
});
206+
207+
describe('mergeTransactionsByTime', () => {
208+
it('sorts unified transactions by time and removes local transactions with confirmed hashes', () => {
209+
const localDuplicate = {
210+
id: 'local-duplicate',
211+
hash: '0xDUPLICATE',
212+
time: 300,
213+
};
214+
const localUnique = {
215+
id: 'local-unique',
216+
hash: '0xlocal',
217+
time: 200,
218+
};
219+
const confirmedDuplicate = {
220+
id: 'confirmed-duplicate',
221+
hash: '0xduplicate',
222+
time: 400,
223+
};
224+
const nonEvm = {
225+
id: 'non-evm',
226+
timestamp: 1,
227+
};
228+
229+
const result = mergeTransactionsByTime(
230+
[localDuplicate, localUnique] as Parameters<
231+
typeof mergeTransactionsByTime
232+
>[0],
233+
[confirmedDuplicate] as Parameters<typeof mergeTransactionsByTime>[1],
234+
[nonEvm] as Parameters<typeof mergeTransactionsByTime>[2],
235+
);
236+
237+
expect(result).toStrictEqual([
238+
{
239+
kind: TransactionKind.NonEvm,
240+
tx: nonEvm,
241+
time: 1000,
242+
},
243+
{
244+
kind: TransactionKind.ConfirmedEvm,
245+
tx: confirmedDuplicate,
246+
time: 400,
247+
},
248+
{
249+
kind: TransactionKind.Evm,
250+
tx: localUnique,
251+
time: 200,
252+
},
253+
]);
254+
});
91255
});

app/selectors/transactionController.test.ts

Lines changed: 130 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,14 @@ import {
66
selectLastWithdrawTokenByType,
77
selectLocalTransactions,
88
selectNonReplacedTransactions,
9+
selectRequiredTransactionIds,
910
selectRequiredTransactionHashes,
11+
selectRequiredTransactions,
1012
selectSwapsTransactions,
13+
selectTransactionBatchMetadataById,
1114
selectTransactionMetadataById,
15+
selectTransactionsByBatchId,
16+
selectTransactionsByIds,
1217
selectSortedTransactions,
1318
selectSortedEVMTransactionsForSelectedAccountGroup,
1419
} from './transactionController';
@@ -125,6 +130,57 @@ describe('TransactionController Selectors', () => {
125130
});
126131
});
127132

133+
describe('selectRequiredTransactionIds', () => {
134+
it('returns required child transaction ids', () => {
135+
const state = {
136+
engine: {
137+
backgroundState: {
138+
TransactionController: {
139+
transactions: [
140+
{
141+
id: 'parent',
142+
requiredTransactionIds: ['child-1', 'child-2'],
143+
},
144+
{
145+
id: 'child-1',
146+
},
147+
],
148+
},
149+
},
150+
},
151+
} as unknown as RootState;
152+
153+
expect(selectRequiredTransactionIds(state)).toStrictEqual(
154+
new Set(['child-1', 'child-2']),
155+
);
156+
});
157+
});
158+
159+
describe('selectRequiredTransactions', () => {
160+
it('returns transactions referenced by required ids', () => {
161+
const child = {
162+
id: 'child',
163+
};
164+
const state = {
165+
engine: {
166+
backgroundState: {
167+
TransactionController: {
168+
transactions: [
169+
{
170+
id: 'parent',
171+
requiredTransactionIds: ['child'],
172+
},
173+
child,
174+
],
175+
},
176+
},
177+
},
178+
} as unknown as RootState;
179+
180+
expect(selectRequiredTransactions(state)).toStrictEqual([child]);
181+
});
182+
});
183+
128184
describe('selectLocalTransactions', () => {
129185
it('filters required child transactions before nonce dedupe', () => {
130186
const activeEvmAddress = '0x0000000000000000000000000000000000000001';
@@ -173,8 +229,8 @@ describe('TransactionController Selectors', () => {
173229
pendingSmartTransactionsForGroup: [],
174230
} as unknown as RootState;
175231

176-
expect(selectLocalTransactions(state).map(({ id }) => id)).toStrictEqual([
177-
'parent',
232+
expect(selectLocalTransactions(state)).toStrictEqual([
233+
expect.objectContaining({ id: 'parent' }),
178234
]);
179235
});
180236
});
@@ -221,6 +277,78 @@ describe('TransactionController Selectors', () => {
221277
});
222278
});
223279

280+
describe('selectTransactionBatchMetadataById', () => {
281+
it('returns the transaction batch matching the given id', () => {
282+
const batch = {
283+
id: 'batch-id',
284+
};
285+
const state = {
286+
engine: {
287+
backgroundState: {
288+
TransactionController: {
289+
transactions: [],
290+
transactionBatches: [batch],
291+
},
292+
},
293+
},
294+
} as unknown as RootState;
295+
296+
expect(selectTransactionBatchMetadataById(state, 'batch-id')).toBe(batch);
297+
});
298+
});
299+
300+
describe('selectTransactionsByIds', () => {
301+
it('returns matching transactions in requested id order', () => {
302+
const first = {
303+
id: 'first',
304+
};
305+
const second = {
306+
id: 'second',
307+
};
308+
const state = {
309+
engine: {
310+
backgroundState: {
311+
TransactionController: {
312+
transactions: [first, second],
313+
},
314+
},
315+
},
316+
} as unknown as RootState;
317+
318+
expect(
319+
selectTransactionsByIds(state, ['second', 'missing', 'first']),
320+
).toStrictEqual([second, first]);
321+
});
322+
});
323+
324+
describe('selectTransactionsByBatchId', () => {
325+
it('returns transactions matching the batch id', () => {
326+
const matchingTransaction = {
327+
id: 'matching',
328+
batchId: 'batch-id',
329+
};
330+
const state = {
331+
engine: {
332+
backgroundState: {
333+
TransactionController: {
334+
transactions: [
335+
matchingTransaction,
336+
{
337+
id: 'other',
338+
batchId: 'other-batch-id',
339+
},
340+
],
341+
},
342+
},
343+
},
344+
} as unknown as RootState;
345+
346+
expect(selectTransactionsByBatchId(state, 'batch-id')).toStrictEqual([
347+
matchingTransaction,
348+
]);
349+
});
350+
});
351+
224352
describe('selectSortedTransactions', () => {
225353
it('merges non-replaced transactions and pending smart transactions and sorts them descending by time', () => {
226354
// Transactions with one replaced transaction and two non-replaced ones

0 commit comments

Comments
 (0)