dev
dart 439 lines 15.8 KB
Raw
1 import 'package:cake_wallet/generated/i18n.dart';
2 import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart';
3 import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart';
4 import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart';
5 import 'package:cake_wallet/view_model/dashboard/date_section_item.dart';
6 import 'package:cake_wallet/view_model/dashboard/order_list_item.dart';
7 import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart';
8 import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart';
9 import 'package:cw_core/crypto_currency.dart';
10 import 'package:cw_core/sync_status.dart';
11 import 'package:cw_core/transaction_direction.dart';
12 import 'package:flutter/material.dart';
13 import 'package:flutter_test/flutter_test.dart';
14 import 'package:intl/intl.dart';
15
16 import '../components/common_test_cases.dart';
17
18 class TransactionsPageRobot {
19 TransactionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester);
20
21 final WidgetTester tester;
22 late CommonTestCases commonTestCases;
23
24 Future<void> isTransactionsPage() async {
25 await commonTestCases.isSpecificPage<TransactionsPage>();
26 await commonTestCases.takeScreenshots('transactions_page');
27 }
28
29 Future<void> confirmTransactionsPageConstantsDisplayProperly() async {
30 await commonTestCases.defaultSleepTime();
31
32 final transactionsPage = tester.widget<TransactionsPage>(find.byType(TransactionsPage));
33 final dashboardViewModel = transactionsPage.dashboardViewModel;
34 if (dashboardViewModel.status is SyncingSyncStatus) {
35 commonTestCases.hasValueKey('transactions_page_syncing_alert_card_key');
36 commonTestCases.hasText(S.current.syncing_wallet_alert_title);
37 commonTestCases.hasText(S.current.syncing_wallet_alert_content);
38 }
39
40 commonTestCases.hasValueKey('transactions_page_header_row_key');
41 commonTestCases.hasText(S.current.transactions);
42 commonTestCases.hasValueKey('transactions_page_header_row_transaction_filter_button_key');
43 }
44
45 Future<void> confirmTransactionHistoryListDisplaysCorrectly(bool hasTxHistoryWhileSyncing) async {
46 try {
47 final transactionsPage = tester.widget<TransactionsPage>(find.byType(TransactionsPage));
48 final dashboardViewModel = transactionsPage.dashboardViewModel;
49
50 await _waitForListToBeReady();
51
52 if (!hasTxHistoryWhileSyncing) {
53 await _waitForSyncToComplete(dashboardViewModel);
54 } else {
55 await tester.pump(Duration(seconds: 2));
56 await tester.pumpAndSettle();
57 }
58
59 await _performComprehensiveItemCheck(dashboardViewModel);
60 } catch (e) {
61 tester.printToConsole('Error in transaction history check: $e');
62 }
63 }
64
65 Future<void> _waitForSyncToComplete(DashboardViewModel dashboardViewModel) async {
66 const maxWaitTime = Duration(minutes: 2);
67 final endTime = DateTime.now().add(maxWaitTime);
68
69 tester.printToConsole('Waiting for wallet to sync...');
70
71 while (DateTime.now().isBefore(endTime)) {
72 await tester.pump(Duration(seconds: 2));
73 await tester.pumpAndSettle();
74
75 if (dashboardViewModel.status is SyncedSyncStatus) {
76 tester.printToConsole('Wallet synced successfully');
77 return;
78 }
79
80 // Check if we have now have transaction items available
81 if (dashboardViewModel.items.isNotEmpty) {
82 tester.printToConsole('Items available while syncing, proceeding with test');
83 return;
84 }
85
86 tester.printToConsole('Sync status: ${dashboardViewModel.status.runtimeType}');
87 }
88
89 tester.printToConsole('Warning: Wallet did not sync within expected time, proceeding anyway');
90 }
91
92 Future<void> _performComprehensiveItemCheck(DashboardViewModel dashboardViewModel) async {
93 try {
94 await _waitForItemsToLoad(dashboardViewModel);
95
96 final itemsToProcess = dashboardViewModel.items.where((item) {
97 if (item is DateSectionItem) return false;
98 if (item is TransactionListItem) {
99 return !(item.hasTokens && item.assetOfTransaction == null);
100 }
101 return true;
102 }).toList();
103
104 if (itemsToProcess.isEmpty) {
105 tester.printToConsole('No transaction items to process - checking for placeholder');
106 _verifyPlaceholder();
107 return;
108 }
109
110 // This is a temporary limit to prevent the test from taking too long
111 final maxItemsToProcess = 100;
112 final itemsToCheck = itemsToProcess.take(maxItemsToProcess).toList();
113
114 tester.printToConsole(
115 'Processing ${itemsToCheck.length} items out of ${itemsToProcess.length} total items',
116 );
117
118 await _processVisibleItems(itemsToCheck, dashboardViewModel);
119
120 // Try to scroll and process more items if needed
121 await _processItemsWithScrolling(itemsToCheck, dashboardViewModel);
122 } catch (e) {
123 tester.printToConsole('Error in comprehensive item check: $e');
124 try {
125 _verifyPlaceholder();
126 } catch (placeholderError) {
127 tester.printToConsole('Could not verify placeholder either: $placeholderError');
128 }
129 }
130 }
131
132 Future<void> _waitForItemsToLoad(DashboardViewModel dashboardViewModel) async {
133 const maxWaitTime = Duration(seconds: 30);
134 final endTime = DateTime.now().add(maxWaitTime);
135
136 while (DateTime.now().isBefore(endTime)) {
137 if (dashboardViewModel.items.isNotEmpty) {
138 tester.printToConsole('Items loaded: ${dashboardViewModel.items.length}');
139 return;
140 }
141
142 // Check if placeholder is shown
143 if (tester.any(find.byKey(ValueKey('transactions_page_placeholder_transactions_text_key')))) {
144 tester.printToConsole('Placeholder is shown - no items to load');
145 return;
146 }
147
148 await tester.pump(Duration(seconds: 1));
149 await tester.pumpAndSettle();
150 }
151
152 tester.printToConsole('Warning: No items loaded and no placeholder shown within expected time');
153 }
154
155 Future<void> _processVisibleItems(
156 List<dynamic> items,
157 DashboardViewModel dashboardViewModel,
158 ) async {
159 try {
160 int processedCount = 0;
161
162 for (var item in items) {
163 try {
164 final keyId = (item.key as ValueKey<String>).value;
165
166 // Check if item is already visible
167 if (tester.any(find.byKey(ValueKey(keyId)))) {
168 tester.printToConsole('Processing visible item: $keyId');
169 await _verifyItemDisplay(item, dashboardViewModel);
170 processedCount++;
171 }
172 } catch (itemError) {
173 tester.printToConsole('Error processing item: $itemError');
174 continue;
175 }
176 }
177
178 tester.printToConsole('Processed $processedCount visible items\n');
179 } catch (e) {
180 tester.printToConsole('Error in visible items processing: $e');
181 }
182 }
183
184 Future<void> _processItemsWithScrolling(
185 List<dynamic> items,
186 DashboardViewModel dashboardViewModel,
187 ) async {
188 try {
189 final scrollableFinder = find.descendant(
190 of: find.byKey(ValueKey('transactions_page_list_view_builder_key')),
191 matching: find.byType(Scrollable),
192 );
193
194 if (!tester.any(scrollableFinder)) {
195 tester.printToConsole('No scrollable found, skipping scroll processing');
196 return;
197 }
198
199 int processedCount = 0;
200 const maxScrollAttempts = 10;
201
202 for (var item in items) {
203 try {
204 final keyId = (item.key as ValueKey<String>).value;
205
206 // Skip if already processed
207 if (tester.any(find.byKey(ValueKey(keyId)))) {
208 continue;
209 }
210
211 // Try to scroll to the item
212 bool found = false;
213 for (int attempt = 0; attempt < maxScrollAttempts; attempt++) {
214 await tester.pumpAndSettle(Duration(milliseconds: 200));
215
216 if (tester.any(find.byKey(ValueKey(keyId)))) {
217 tester.printToConsole('Found item after scrolling: $keyId');
218 await _verifyItemDisplay(item, dashboardViewModel);
219 processedCount++;
220 found = true;
221 break;
222 }
223
224 // Perform scroll
225 try {
226 await tester.drag(scrollableFinder, Offset(0, -100));
227 await tester.pumpAndSettle(Duration(milliseconds: 300));
228 } catch (scrollError) {
229 tester.printToConsole('Scroll failed: $scrollError');
230 break;
231 }
232 }
233
234 if (!found) {
235 tester.printToConsole('Could not find item after scrolling: $keyId');
236 }
237 } catch (itemError) {
238 tester.printToConsole('Error processing item with scrolling: $itemError');
239 continue;
240 }
241 }
242
243 tester.printToConsole('Processed $processedCount items with scrolling');
244 } catch (e) {
245 tester.printToConsole('Error in scroll processing: $e');
246 }
247 }
248
249 void _verifyPlaceholder() {
250 commonTestCases.hasValueKey('transactions_page_placeholder_transactions_text_key');
251 commonTestCases.hasText(S.current.placeholder_transactions);
252 }
253
254 Future<void> _waitForListToBeReady() async {
255 const maxWaitAttempts = 10;
256 int attempts = 0;
257
258 while (attempts < maxWaitAttempts) {
259 await tester.pumpAndSettle(Duration(milliseconds: 500));
260
261 // Check if the list view is present
262 final listViewFinder = find.byKey(ValueKey('transactions_page_list_view_builder_key'));
263 if (tester.any(listViewFinder)) {
264 tester.printToConsole('List view is ready');
265 return;
266 }
267
268 attempts++;
269 tester.printToConsole('Waiting for list view to be ready, attempt $attempts\n');
270 }
271
272 tester.printToConsole('List view did not become ready within expected time');
273 }
274
275 Future<void> _verifyItemDisplay(dynamic item, DashboardViewModel dashboardViewModel) async {
276 // Execute the proper check depending on item type.
277 switch (item.runtimeType) {
278 case TransactionListItem:
279 final transactionItem = item as TransactionListItem;
280 tester.printToConsole(transactionItem.formattedTitle);
281 tester.printToConsole(transactionItem.formattedFiatAmount);
282 tester.printToConsole('\n');
283 await _verifyTransactionListItemDisplay(transactionItem, dashboardViewModel);
284 break;
285
286 case AnonpayTransactionListItem:
287 await _verifyAnonpayTransactionListItemDisplay(item as AnonpayTransactionListItem);
288 break;
289
290 case TradeListItem:
291 await _verifyTradeListItemDisplay(item as TradeListItem);
292 break;
293
294 case OrderListItem:
295 await _verifyOrderListItemDisplay(item as OrderListItem);
296 break;
297
298 default:
299 tester.printToConsole('Unhandled item type: ${item.runtimeType}');
300 }
301 }
302
303 Future<void> _verifyTransactionListItemDisplay(
304 TransactionListItem item,
305 DashboardViewModel dashboardViewModel,
306 ) async {
307 final keyId =
308 '${dashboardViewModel.type.name}_transaction_history_item_${item.transaction.id}_key';
309
310 if (!tester.any(find.byKey(ValueKey(keyId)))) {
311 tester.printToConsole(
312 'Could not find transaction item with key: $keyId for transaction ${item.transaction.id}',
313 );
314 return;
315 }
316
317 if (item.hasTokens && item.assetOfTransaction == null) return;
318
319 try {
320 //* ==============Confirm it has the right key for this item ========
321 commonTestCases.hasValueKey(keyId);
322
323 //* ======Confirm it displays the properly formatted amount==========
324 commonTestCases.findWidgetViaDescendant(
325 of: find.byKey(ValueKey(keyId)),
326 matching: find.text(item.formattedCryptoAmount),
327 );
328
329 //* ======Confirm it displays the properly formatted date============
330 final formattedDate = DateFormat('HH:mm').format(item.transaction.date);
331 commonTestCases.findWidgetViaDescendant(
332 of: find.byKey(ValueKey(keyId)),
333 matching: find.text(formattedDate),
334 );
335
336 //* ======Confirm it displays the properly formatted fiat amount=====
337 final formattedFiatAmount =
338 dashboardViewModel.balanceViewModel.isFiatDisabled ? '' : item.formattedFiatAmount;
339 if (formattedFiatAmount.isNotEmpty) {
340 commonTestCases.findWidgetViaDescendant(
341 of: find.byKey(ValueKey(keyId)),
342 matching: find.text(formattedFiatAmount),
343 );
344 }
345
346 //* ======Confirm it displays the right image based on the transaction direction=====
347 final imageToUse = item.transaction.direction == TransactionDirection.incoming
348 ? 'assets/images/down_arrow.png'
349 : 'assets/images/up_arrow.png';
350
351 find.widgetWithImage(Container, AssetImage(imageToUse));
352 } catch (e) {
353 tester.printToConsole('Error verifying transaction item ${item.transaction.id}: $e');
354 }
355 }
356
357 Future<void> _verifyAnonpayTransactionListItemDisplay(AnonpayTransactionListItem item) async {
358 final keyId = 'anonpay_invoice_transaction_list_item_${item.transaction.invoiceId}_key';
359
360 //* ==============Confirm it has the right key for this item ========
361 commonTestCases.hasValueKey(keyId);
362
363 //* ==============Confirm it displays the correct provider =========================
364 commonTestCases.hasText(item.transaction.provider);
365
366 //* ===========Confirm it displays the properly formatted amount with currency ========
367 final currency = item.transaction.fiatAmount != null
368 ? item.transaction.fiatEquiv ?? ''
369 : CryptoCurrency.fromFullName(item.transaction.coinTo).name.toUpperCase();
370
371 final amount =
372 item.transaction.fiatAmount?.toString() ?? (item.transaction.amountTo?.toString() ?? '');
373
374 final amountCurrencyText = amount + ' ' + currency;
375
376 commonTestCases.hasText(amountCurrencyText);
377
378 //* ======Confirm it displays the properly formatted date=================
379 final formattedDate = DateFormat('HH:mm').format(item.transaction.createdAt);
380 commonTestCases.hasText(formattedDate);
381
382 //* ===============Confirm it displays the right image====================
383 find.widgetWithImage(ClipRRect, AssetImage('assets/images/trocador.png'));
384 }
385
386 Future<void> _verifyTradeListItemDisplay(TradeListItem item) async {
387 final keyId = 'trade_list_item_${item.trade.id}_key';
388 final from = item.trade.from?.toString() ?? '';
389 final to = item.trade.to?.toString() ?? '';
390
391 //* ==============Confirm it has the right key for this item ========
392 commonTestCases.hasValueKey(keyId);
393
394 //* ==============Confirm it displays the correct provider =========================
395 final conversionFlow = '$from$to';
396
397 commonTestCases.hasText(conversionFlow);
398
399 //* ===========Confirm it displays the properly formatted amount with its crypto tag ========
400
401 final amountCryptoText = item.tradeFormattedAmount + ' ' + from;
402
403 commonTestCases.hasText(amountCryptoText);
404
405 //* ======Confirm it displays the properly formatted date=================
406 final createdAtFormattedDate =
407 item.trade.createdAt != null ? DateFormat('HH:mm').format(item.trade.createdAt!) : null;
408
409 if (createdAtFormattedDate != null) {
410 commonTestCases.hasText(createdAtFormattedDate);
411 }
412
413 //* ===============Confirm it displays the right image====================
414 commonTestCases.hasValueKey(item.trade.provider.image);
415 }
416
417 Future<void> _verifyOrderListItemDisplay(OrderListItem item) async {
418 final keyId = 'order_list_item_${item.order.id}_key';
419
420 //* ==============Confirm it has the right key for this item ========
421 commonTestCases.hasValueKey(keyId);
422
423 //* ==============Confirm it displays the correct provider =========================
424 final orderFlow = '${item.order.from!}${item.order.to}';
425
426 commonTestCases.hasText(orderFlow);
427
428 //* ===========Confirm it displays the properly formatted amount with its crypto tag ========
429
430 final amountCryptoText = item.orderFormattedAmount + ' ' + item.order.to!;
431
432 commonTestCases.hasText(amountCryptoText);
433
434 //* ======Confirm it displays the properly formatted date=================
435 final createdAtFormattedDate = DateFormat('HH:mm').format(item.order.createdAt);
436
437 commonTestCases.hasText(createdAtFormattedDate);
438 }
439 }