diff --git a/test/features/transaction/transaction_tile_test.dart b/test/features/transaction/transaction_tile_test.dart new file mode 100644 index 00000000..5e5b041f --- /dev/null +++ b/test/features/transaction/transaction_tile_test.dart @@ -0,0 +1,202 @@ +import 'package:didpay/features/transaction/transaction.dart'; +import 'package:didpay/features/transaction/transaction_details_page.dart'; +import 'package:didpay/features/transaction/transaction_notifier.dart'; +import 'package:didpay/features/transaction/transaction_tile.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../helpers/mocks.dart'; +import '../../helpers/test_data.dart'; +import '../../helpers/widget_helpers.dart'; + +void main() { + group('TransactionTile', () { + final sendTransaction = TestData.getTransaction(); + final depositTransaction = TestData.getTransaction( + type: TransactionType.deposit, + ); + final withdrawTransaction = TestData.getTransaction( + type: TransactionType.withdraw, + ); + + const mockTransactionNotifierWithSendTransaction = + MockTransactionNotifierWithData( + transactionType: TransactionType.send, + ); + const mockTransactionNotifierWithDepositTransaction = + MockTransactionNotifierWithData( + transactionType: TransactionType.deposit, + ); + const mockTransactionNotifierWithWithdrawTransaction = + MockTransactionNotifierWithData( + transactionType: TransactionType.withdraw, + ); + const mockTransactionNotifierWithNullData = + MockTransactionNotifierWithNullData(); + const mockTransactionNotifierWithError = MockTransactionNotifierWithError(); + + late MockTransactionNotifier mockSendTransactionNotifier; + late MockTransactionNotifier mockDepositTransactionNotifier; + late MockTransactionNotifier mockWithdrawTransactionNotifier; + late MockTransactionNotifier nullMockTransactionNotifier; + late MockTransactionNotifier erroringMockTransactionNotifier; + + setUp(() { + mockSendTransactionNotifier = + MockTransactionNotifier(() => sendTransaction); + mockDepositTransactionNotifier = + MockTransactionNotifier(() => depositTransaction); + mockWithdrawTransactionNotifier = + MockTransactionNotifier(() => withdrawTransaction); + nullMockTransactionNotifier = MockTransactionNotifier(); + erroringMockTransactionNotifier = MockTransactionNotifier( + () => throw StateError('Error loading transaction'), + ); + }); + + Widget transactionTileTestWidget({ + required MockTransactionNotifierType mockTransactionNotifierType, + }) => + WidgetHelpers.testableWidget( + child: TransactionTile( + pfi: TestData.getPfi('did:dht:pfiDid'), + exchangeId: 'rfq_01ha835rhefwmagsknrrhvaa0k', + ), + overrides: [ + transactionProvider.overrideWith( + () => switch (mockTransactionNotifierType) { + MockTransactionNotifierWithData() => switch ( + mockTransactionNotifierType.transactionType) { + TransactionType.send => mockSendTransactionNotifier, + TransactionType.deposit => mockDepositTransactionNotifier, + TransactionType.withdraw => mockWithdrawTransactionNotifier, + }, + MockTransactionNotifierWithNullData() => + nullMockTransactionNotifier, + MockTransactionNotifierWithError() => + erroringMockTransactionNotifier, + }, + ), + ], + ); + + testWidgets('should show payin and payout currencies', (tester) async { + await tester.pumpWidget( + transactionTileTestWidget( + mockTransactionNotifierType: + mockTransactionNotifierWithSendTransaction, + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('AUD → BTC'), findsOneWidget); + }); + + testWidgets('should show transaction status', (tester) async { + await tester.pumpWidget( + transactionTileTestWidget( + mockTransactionNotifierType: + mockTransactionNotifierWithSendTransaction, + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('Order submitted'), findsOneWidget); + }); + + testWidgets('should show error if transaction is null', (tester) async { + await tester.pumpWidget( + transactionTileTestWidget( + mockTransactionNotifierType: mockTransactionNotifierWithNullData, + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('No transactions found'), findsOneWidget); + }); + + testWidgets('should show error when transaction fetch returns an error', + (tester) async { + await tester.pumpWidget( + transactionTileTestWidget( + mockTransactionNotifierType: mockTransactionNotifierWithError, + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('Bad state: Error loading transaction'), findsOneWidget); + }); + + group('should show transaction amount in correct format for ', () { + testWidgets('send transactions', (tester) async { + await tester.pumpWidget( + transactionTileTestWidget( + mockTransactionNotifierType: + mockTransactionNotifierWithSendTransaction, + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('-100.01 AUD'), findsOneWidget); + }); + + testWidgets('deposit transactions', (tester) async { + await tester.pumpWidget( + transactionTileTestWidget( + mockTransactionNotifierType: + mockTransactionNotifierWithDepositTransaction, + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('+0.12 BTC'), findsOneWidget); + }); + + testWidgets('withdraw transactions', (tester) async { + await tester.pumpWidget( + transactionTileTestWidget( + mockTransactionNotifierType: + mockTransactionNotifierWithWithdrawTransaction, + ), + ); + await tester.pumpAndSettle(); + + expect(find.text('+100.01 AUD'), findsOneWidget); + }); + }); + + testWidgets('should navigate to transaction details page on tap', + (tester) async { + await tester.pumpWidget( + transactionTileTestWidget( + mockTransactionNotifierType: + mockTransactionNotifierWithSendTransaction, + ), + ); + + await tester.pumpAndSettle(); + await tester.tap(find.widgetWithText(ListTile, 'AUD → BTC')); + + await tester.pumpAndSettle(); + expect(find.byType(TransactionDetailsPage), findsOneWidget); + }); + }); +} + +sealed class MockTransactionNotifierType { + const MockTransactionNotifierType(); +} + +class MockTransactionNotifierWithData extends MockTransactionNotifierType { + const MockTransactionNotifierWithData({required this.transactionType}); + + final TransactionType transactionType; +} + +class MockTransactionNotifierWithNullData extends MockTransactionNotifierType { + const MockTransactionNotifierWithNullData(); +} + +class MockTransactionNotifierWithError extends MockTransactionNotifierType { + const MockTransactionNotifierWithError(); +} diff --git a/test/helpers/mocks.dart b/test/helpers/mocks.dart index aff98b3a..64687fcb 100644 --- a/test/helpers/mocks.dart +++ b/test/helpers/mocks.dart @@ -53,7 +53,13 @@ class MockFeatureFlagsNotifier extends StateNotifier> class MockTransactionNotifier extends AutoDisposeFamilyAsyncNotifier< Transaction?, TransactionProviderParameters> with Mock implements TransactionNotifier { - MockTransactionNotifier(); + final Transaction? Function()? transactionBuilder; + + MockTransactionNotifier([this.transactionBuilder]); + + @override + FutureOr build(TransactionProviderParameters arg) async => + transactionBuilder?.call(); } class MockTbdexQuoteNotifier extends AutoDisposeAsyncNotifier diff --git a/test/helpers/test_data.dart b/test/helpers/test_data.dart index 07e38da4..aa32816f 100644 --- a/test/helpers/test_data.dart +++ b/test/helpers/test_data.dart @@ -3,6 +3,7 @@ import 'dart:convert'; import 'package:didpay/features/account/account_balance.dart'; import 'package:didpay/features/feature_flags/feature_flag.dart'; import 'package:didpay/features/pfis/pfi.dart'; +import 'package:didpay/features/transaction/transaction.dart'; import 'package:json_schema/json_schema.dart'; import 'package:tbdex/tbdex.dart'; import 'package:typeid/typeid.dart'; @@ -160,4 +161,17 @@ class TestData { } '''), ); + + static Transaction getTransaction({ + TransactionType type = TransactionType.send, + }) => + Transaction( + payinAmount: '100.01', + payoutAmount: '0.12', + payinCurrency: 'AUD', + payoutCurrency: 'BTC', + createdAt: DateTime(2024), + type: type, + status: TransactionStatus.orderSubmitted, + ); }