From 5476b77882dee09bb1e84a0273c33dc3e5210dcd Mon Sep 17 00:00:00 2001 From: Kirah Sapong Date: Thu, 15 Feb 2024 11:21:00 -0800 Subject: [PATCH] Add review request page and test --- .../payments/review_request_page.dart | 171 ++++++++++++++++++ .../payments/review_request_page_test.dart | 70 +++++++ 2 files changed, 241 insertions(+) create mode 100644 lib/features/payments/review_request_page.dart create mode 100644 test/features/payments/review_request_page_test.dart diff --git a/lib/features/payments/review_request_page.dart b/lib/features/payments/review_request_page.dart new file mode 100644 index 00000000..782a6b15 --- /dev/null +++ b/lib/features/payments/review_request_page.dart @@ -0,0 +1,171 @@ +import 'package:didpay/features/home/transaction.dart'; +import 'package:didpay/l10n/app_localizations.dart'; +import 'package:didpay/shared/fee_details.dart'; +import 'package:didpay/shared/success_page.dart'; +import 'package:didpay/shared/theme/grid.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_hooks/flutter_hooks.dart'; +import 'package:intl/intl.dart'; + +class ReviewRequestPage extends HookWidget { + final String inputAmount; + final String outputAmount; + final String inputCurrency; + final String outputCurrency; + final String exchangeRate; + final String serviceFee; + final String bankName; + final Map formData; + final String transactionType; + + const ReviewRequestPage({ + required this.inputAmount, + required this.outputAmount, + required this.inputCurrency, + required this.outputCurrency, + required this.exchangeRate, + required this.serviceFee, + required this.bankName, + required this.formData, + required this.transactionType, + super.key, + }); + + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar(), + body: SafeArea( + child: Padding( + padding: const EdgeInsets.symmetric(horizontal: Grid.side), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Expanded( + child: SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + _buildHeader(context), + const SizedBox(height: Grid.md), + _buildAmounts(context), + const SizedBox(height: Grid.md), + _buildFeeDetails(context), + const SizedBox(height: Grid.md), + _buildBankDetails(context), + ])), + ), + Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + FilledButton( + onPressed: () { + Navigator.of(context).push( + MaterialPageRoute( + builder: (context) => SuccessPage( + text: Loc.of(context).yourRequestWasSubmitted, + ), + ), + ); + }, + child: Text(Loc.of(context).submit), + ), + ], + ), + ], + )))); + } + + Widget _buildHeader(BuildContext context) { + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text( + Loc.of(context).reviewYourRequest, + style: Theme.of(context).textTheme.titleLarge, + ), + const SizedBox(height: Grid.xs), + Text(Loc.of(context).makeSureInfoIsCorrect, + style: Theme.of(context).textTheme.bodyLarge), + ]); + } + + Widget _buildAmounts(BuildContext context) { + return Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + NumberFormat.simpleCurrency().format(double.parse(inputAmount)), + style: Theme.of(context).textTheme.displaySmall, + ), + const SizedBox(width: Grid.xs), + Text( + inputCurrency, + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + const SizedBox(height: Grid.xxs), + Text( + transactionType == Type.deposit + ? Loc.of(context).youPay + : Loc.of(context).withdrawAmount, + style: Theme.of(context).textTheme.bodyLarge, + ), + const SizedBox(height: Grid.xs), + Row( + crossAxisAlignment: CrossAxisAlignment.baseline, + textBaseline: TextBaseline.alphabetic, + children: [ + Text( + NumberFormat.simpleCurrency().format(double.parse(outputAmount)), + style: Theme.of(context).textTheme.displaySmall, + ), + const SizedBox(width: Grid.xs), + Text( + outputCurrency, + style: Theme.of(context).textTheme.headlineMedium, + ), + ], + ), + const SizedBox(height: Grid.xxs), + Text( + transactionType == Type.deposit + ? Loc.of(context).depositAmount + : Loc.of(context).youGet, + style: Theme.of(context).textTheme.bodyLarge, + ) + ]); + } + + Widget _buildFeeDetails(BuildContext context) { + return FeeDetails( + originCurrency: Loc.of(context).usd, + destinationCurrency: inputCurrency != Loc.of(context).usd + ? inputCurrency + : outputCurrency, + exchangeRate: exchangeRate, + serviceFee: double.parse(serviceFee).toStringAsFixed(2), + total: inputCurrency != Loc.of(context).usd + ? (double.parse(inputAmount) + double.parse(serviceFee)) + .toStringAsFixed(2) + : (double.parse(outputAmount) + double.parse(serviceFee)) + .toStringAsFixed(2)); + } + + Widget _buildBankDetails(BuildContext context) { + return Column(children: [ + Text(bankName), + const SizedBox(height: Grid.xxs), + Text(obscureAccountNumber(formData['accountNumber']!)), + ]); + } + + String obscureAccountNumber(String input) { + if (input.length <= 4) { + return input; + } + return '${'•' * (input.length - 4)} ${input.substring(input.length - 4)}'; + } +} diff --git a/test/features/payments/review_request_page_test.dart b/test/features/payments/review_request_page_test.dart new file mode 100644 index 00000000..87144b5f --- /dev/null +++ b/test/features/payments/review_request_page_test.dart @@ -0,0 +1,70 @@ +import 'package:didpay/features/payments/review_request_page.dart'; +import 'package:didpay/shared/fee_details.dart'; +import 'package:didpay/shared/success_page.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../../helpers/widget_helpers.dart'; + +void main() { + group('ReviewRequestPage', () { + const mockReviewRequestPage = ReviewRequestPage( + inputAmount: '1.00', + inputCurrency: 'USD', + exchangeRate: '17', + outputAmount: '17.00', + outputCurrency: 'MXN', + transactionType: 'Deposit', + serviceFee: '9.0', + bankName: 'ABC Bank', + formData: {'accountNumber': '1234567890'}, + ); + + testWidgets('should show input and output amounts', (tester) async { + await tester.pumpWidget( + WidgetHelpers.testableWidget(child: mockReviewRequestPage)); + + expect(find.text('\$1.00'), findsOneWidget); + expect(find.text('USD'), findsOneWidget); + expect(find.text('\$17.00'), findsOneWidget); + expect(find.text('MXN'), findsOneWidget); + }); + + testWidgets('should show fee table with service fee and total', + (tester) async { + await tester.pumpWidget( + WidgetHelpers.testableWidget(child: mockReviewRequestPage)); + + expect(find.byType(FeeDetails), findsOneWidget); + expect(find.text('9.00 MXN'), findsOneWidget); + expect(find.text('26.00 MXN'), findsOneWidget); + }); + + testWidgets('should show bank name', (tester) async { + await tester.pumpWidget( + WidgetHelpers.testableWidget(child: mockReviewRequestPage)); + + expect(find.text('ABC Bank'), findsOneWidget); + }); + + testWidgets('should show obscured account number', (tester) async { + await tester.pumpWidget( + WidgetHelpers.testableWidget(child: mockReviewRequestPage)); + + expect(find.textContaining('•'), findsOneWidget); + expect(find.textContaining('7890'), findsOneWidget); + expect(find.textContaining('1234567890'), findsNothing); + }); + + testWidgets('should show success page on tap of submit button', + (tester) async { + await tester.pumpWidget( + WidgetHelpers.testableWidget(child: mockReviewRequestPage)); + + await tester.tap(find.text('Submit')); + await tester.pumpAndSettle(); + + expect(find.byType(SuccessPage), findsOneWidget); + expect(find.text('Your request was submitted!'), findsOneWidget); + }); + }); +}