From ccf54ab6366b4582a9d67908e7312047234a7297 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 21 Jun 2024 11:55:07 +0100 Subject: [PATCH 01/69] feat: Integration tests setup and tests for Disclaimer, Welcome and Setup Pin Code pages --- cw_bitcoin/pubspec.yaml | 7 +- cw_core/pubspec.yaml | 2 +- integration_test/app_test.dart | 77 +++++ .../helpers/components/common_checks.dart | 45 +++ integration_test/helpers/mocks.dart | 25 ++ integration_test/helpers/test_helpers.dart | 100 ++++++ .../robots/disclaimer_page_robot.dart | 40 +++ .../robots/setup_pin_code_robot.dart | 56 ++++ .../robots/welcome_page_robot.dart | 44 +++ ios/Podfile.lock | 8 +- ios/Runner.xcodeproj/project.pbxproj | 8 +- .../xcshareddata/xcschemes/Runner.xcscheme | 2 +- lib/main.dart | 54 ++-- .../screens/disclaimer/disclaimer_page.dart | 55 ++-- lib/src/screens/pin_code/pin_code_widget.dart | 104 ++++--- lib/src/screens/welcome/welcome_page.dart | 214 ++++++------- lib/src/widgets/primary_button.dart | 289 ++++++++++-------- macos/Flutter/GeneratedPluginRegistrant.swift | 2 +- pubspec_base.yaml | 7 +- test_driver/integration_test.dart | 34 +++ 20 files changed, 831 insertions(+), 342 deletions(-) create mode 100644 integration_test/app_test.dart create mode 100644 integration_test/helpers/components/common_checks.dart create mode 100644 integration_test/helpers/mocks.dart create mode 100644 integration_test/helpers/test_helpers.dart create mode 100644 integration_test/robots/disclaimer_page_robot.dart create mode 100644 integration_test/robots/setup_pin_code_robot.dart create mode 100644 integration_test/robots/welcome_page_robot.dart create mode 100644 test_driver/integration_test.dart diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 40f3c6e29b..62afe3df00 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -43,9 +43,10 @@ dependencies: git: url: https://github.com/cake-tech/ledger-bitcoin sp_scanner: - git: - url: https://github.com/cake-tech/sp_scanner - ref: sp_v2.0.0 + path: ../../sp_scanner + # git: + # url: https://github.com/cake-tech/sp_scanner + # ref: sp_v2.0.0 dev_dependencies: diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 51d671dc7b..0a181a9c9a 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -13,7 +13,7 @@ dependencies: flutter: sdk: flutter http: ^1.1.0 - file: ^6.1.4 + file: ^7.0.0 path_provider: ^2.0.11 mobx: ^2.0.7+4 flutter_mobx: ^2.0.6+1 diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart new file mode 100644 index 0000000000..b3a02f176f --- /dev/null +++ b/integration_test/app_test.dart @@ -0,0 +1,77 @@ +import 'package:cake_wallet/main.dart' as app; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'robots/disclaimer_page_robot.dart'; +import 'robots/setup_pin_code_robot.dart'; +import 'robots/welcome_page_robot.dart'; + +Future restoreFlutterError() async { + final originalOnError = FlutterError.onError!; + + // restore FlutterError.onError + FlutterError.onError = (FlutterErrorDetails details) { + originalOnError(details); + }; +} + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + DisclaimerPageRobot disclaimerPageRobot; + WelcomePageRobot welcomePageRobot; + SetupPinCodeRobot setupPinCodeRobot; + + group('Startup Test', () { + testWidgets('Unauthenticated Startup flow', (tester) async { + disclaimerPageRobot = DisclaimerPageRobot(tester); + welcomePageRobot = WelcomePageRobot(tester); + setupPinCodeRobot = SetupPinCodeRobot(tester); + + await app.main(); + await tester.pumpAndSettle(); + + // --------- Disclaimer Page ------------ + // Confirm initial defaults + await disclaimerPageRobot.isDisclaimerPage(); + disclaimerPageRobot.hasCheckIcon(false); + disclaimerPageRobot.hasDisclaimerCheckbox(); + + // Tap checkbox to accept disclaimer + await disclaimerPageRobot.tapDisclaimerCheckbox(); + + // Confirm that page has been updated with the check mark icon in checkbox + disclaimerPageRobot.hasCheckIcon(true); + + // Tap accept button + await disclaimerPageRobot.tapAcceptButton(); + tester.printToConsole('Routing to Welcome Page'); + + // --------- Welcome Page --------------- + // Confirm initial defaults - Widgets to be displayed etc + await welcomePageRobot.isWelcomePage(); + welcomePageRobot.confirmActionButtonsDisplay(); + + // Confirm routing to Create Wallet Page works + await welcomePageRobot.navigateToCreateNewWalletPage(); + await welcomePageRobot.backAndVerify(); + + // Confirm routing to Restore Wallet Page works + await welcomePageRobot.navigateToRestoreWalletPage(); + await welcomePageRobot.backAndVerify(); + + // Route to restore wallet to continue flow + await welcomePageRobot.navigateToRestoreWalletPage(); + tester.printToConsole('Routing to Restore Wallet Page'); + + // ----------- SetupPinCode Page ------------- + // Confirm initial defaults - Widgets to be displayed etc + await setupPinCodeRobot.isSetupPinCodePage(); + setupPinCodeRobot.hasPinCodeWidget(); + setupPinCodeRobot.hasTitle(); + setupPinCodeRobot.hasNumberButtonsVisible(); + + await setupPinCodeRobot.enterPinCode(); + }); + }); +} diff --git a/integration_test/helpers/components/common_checks.dart b/integration_test/helpers/components/common_checks.dart new file mode 100644 index 0000000000..679874e01a --- /dev/null +++ b/integration_test/helpers/components/common_checks.dart @@ -0,0 +1,45 @@ +// ignore_for_file: public_member_api_docs, sort_constructors_first +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class CommonTestCases { + WidgetTester tester; + CommonTestCases(this.tester); + + Future isSpecificPage() async { + await tester.pumpAndSettle(); + hasType(); + } + + Future tapItemByKey(String key) async { + final widget = find.byKey(ValueKey(key)); + await tester.tap(widget); + await tester.pumpAndSettle(); + } + + void hasText(String text) { + final textWidget = find.text(text); + expect(textWidget, findsOneWidget); + } + + void hasType() { + final typeWidget = find.byType(T); + expect(typeWidget, findsOneWidget); + } + + void hasKey(String key) { + final typeWidget = find.byKey(ValueKey(key)); + expect(typeWidget, findsOneWidget); + } + + Future goBack() async { + tester.printToConsole('Routing back to previous screen'); + final NavigatorState navigator = tester.state(find.byType(Navigator)); + navigator.pop(); + await tester.pumpAndSettle(); + } + + void defaultSleepTime() => sleep(Duration(seconds: 2)); +} diff --git a/integration_test/helpers/mocks.dart b/integration_test/helpers/mocks.dart new file mode 100644 index 0000000000..01259bcc8d --- /dev/null +++ b/integration_test/helpers/mocks.dart @@ -0,0 +1,25 @@ +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/core/secure_storage.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/authentication_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/store/wallet_list_store.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; +import 'package:hive/hive.dart'; +import 'package:mocktail/mocktail.dart'; + +class MockAppStore extends Mock implements AppStore{} +class MockAuthService extends Mock implements AuthService{} +class MockSettingsStore extends Mock implements SettingsStore {} +class MockAuthenticationStore extends Mock implements AuthenticationStore{} +class MockWalletListStore extends Mock implements WalletListStore{} + + + +class MockLinkViewModel extends Mock implements LinkViewModel {} + +class MockHiveInterface extends Mock implements HiveInterface {} + +class MockHiveBox extends Mock implements Box {} + +class MockSecureStorage extends Mock implements SecureStorage{} \ No newline at end of file diff --git a/integration_test/helpers/test_helpers.dart b/integration_test/helpers/test_helpers.dart new file mode 100644 index 0000000000..979ff60ff3 --- /dev/null +++ b/integration_test/helpers/test_helpers.dart @@ -0,0 +1,100 @@ +import 'package:cake_wallet/core/auth_service.dart'; +import 'package:cake_wallet/core/secure_storage.dart'; +import 'package:cake_wallet/di.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cake_wallet/store/authentication_store.dart'; +import 'package:cake_wallet/store/settings_store.dart'; +import 'package:cake_wallet/store/wallet_list_store.dart'; +import 'package:cake_wallet/view_model/link_view_model.dart'; +import 'package:hive/hive.dart'; +import 'package:mocktail/mocktail.dart'; + +import 'mocks.dart'; + +class TestHelpers { + static void setup() { + // Fallback values can also be declared here + registerDependencies(); + } + + static void registerDependencies() { + getAndRegisterAppStore(); + getAndRegisterAuthService(); + getAndRegisterSettingsStore(); + getAndRegisterAuthenticationStore(); + getAndRegisterWalletListStore(); + + getAndRegisterLinkViewModel(); + getAndRegisterSecureStorage(); + getAndRegisterHiveInterface(); + } + + static MockSettingsStore getAndRegisterSettingsStore() { + _removeRegistrationIfExists(); + final service = MockSettingsStore(); + getIt.registerSingleton(service); + return service; + } + + static MockAppStore getAndRegisterAppStore() { + _removeRegistrationIfExists(); + final service = MockAppStore(); + final settingsStore = getAndRegisterSettingsStore(); + + when(() => service.settingsStore).thenAnswer((invocation) => settingsStore); + getIt.registerSingleton(service); + return service; + } + + static MockAuthService getAndRegisterAuthService() { + _removeRegistrationIfExists(); + final service = MockAuthService(); + getIt.registerSingleton(service); + return service; + } + + static MockAuthenticationStore getAndRegisterAuthenticationStore() { + _removeRegistrationIfExists(); + final service = MockAuthenticationStore(); + when(() => service.state).thenReturn(AuthenticationState.uninitialized); + getIt.registerSingleton(service); + return service; + } + + static MockWalletListStore getAndRegisterWalletListStore() { + _removeRegistrationIfExists(); + final service = MockWalletListStore(); + getIt.registerSingleton(service); + return service; + } + + static MockLinkViewModel getAndRegisterLinkViewModel() { + _removeRegistrationIfExists(); + final service = MockLinkViewModel(); + getIt.registerSingleton(service); + return service; + } + + static MockHiveInterface getAndRegisterHiveInterface() { + _removeRegistrationIfExists(); + final service = MockHiveInterface(); + final box = MockHiveBox(); + getIt.registerSingleton(service); + return service; + } + + static MockSecureStorage getAndRegisterSecureStorage() { + _removeRegistrationIfExists(); + final service = MockSecureStorage(); + getIt.registerSingleton(service); + return service; + } + + static void _removeRegistrationIfExists() { + if (getIt.isRegistered()) { + getIt.unregister(); + } + } + + static void tearDown() => getIt.reset(); +} diff --git a/integration_test/robots/disclaimer_page_robot.dart b/integration_test/robots/disclaimer_page_robot.dart new file mode 100644 index 0000000000..1f7bfaf816 --- /dev/null +++ b/integration_test/robots/disclaimer_page_robot.dart @@ -0,0 +1,40 @@ +import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../helpers/components/common_checks.dart'; + +class DisclaimerPageRobot { + DisclaimerPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isDisclaimerPage() async { + await commonTestCases.isSpecificPage(); + } + + void hasCheckIcon(bool hasBeenTapped) { + // The checked Icon should not be available initially, until user taps the checkbox + final checkIcon = find.byKey(ValueKey('disclaimer_check_icon_key')); + expect(checkIcon, hasBeenTapped ? findsOneWidget : findsNothing); + } + + void hasDisclaimerCheckbox() { + final checkBox = find.byKey(ValueKey('disclaimer_check_key')); + expect(checkBox, findsOneWidget); + } + + Future tapDisclaimerCheckbox() async { + final checkBox = find.byKey(ValueKey('disclaimer_check_key')); + await tester.tap(checkBox); + await tester.pumpAndSettle(); + commonTestCases.defaultSleepTime(); + } + + Future tapAcceptButton() async { + final checkBox = find.byKey(ValueKey('disclaimer_accept_button_key')); + await tester.tap(checkBox); + await tester.pumpAndSettle(); + } +} diff --git a/integration_test/robots/setup_pin_code_robot.dart b/integration_test/robots/setup_pin_code_robot.dart new file mode 100644 index 0000000000..911ee028f2 --- /dev/null +++ b/integration_test/robots/setup_pin_code_robot.dart @@ -0,0 +1,56 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; +import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../helpers/components/common_checks.dart'; + +class SetupPinCodeRobot { + SetupPinCodeRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isSetupPinCodePage() async { + await commonTestCases.isSpecificPage(); + } + + void hasPinCodeWidget() { + final pinCodeWidget = find.byType(PinCodeWidget); + expect(pinCodeWidget, findsOneWidget); + } + + void hasTitle() { + commonTestCases.hasText(S.current.setup_pin); + } + + void hasNumberButtonsVisible() { + // Confirmation for buttons 1-9 + for (var i = 1; i < 10; i++) { + commonTestCases.hasKey('pin_code_button_${i}_key'); + } + + // Confirmation for 0 button + commonTestCases.hasKey('pin_code_button_0_key'); + } + + Future pushPinButton(int index) async { + final button = find.byKey(ValueKey('pin_code_button_${index}_key')); + await tester.tap(button); + await tester.pumpAndSettle(); + commonTestCases.defaultSleepTime(); + } + + Future enterPinCode() async { + final PinCodeState pinCodeState = tester.state(find.byType(PinCodeWidget)); + print(pinCodeState.pin); + final codeToUse = [0, 8, 0, 1]; + await codeToUse.map((code) async { + await pushPinButton(code); + }); + + commonTestCases.defaultSleepTime(); + expect(pinCodeState.pin, '0801'); + } +} diff --git a/integration_test/robots/welcome_page_robot.dart b/integration_test/robots/welcome_page_robot.dart new file mode 100644 index 0000000000..43b4969aa4 --- /dev/null +++ b/integration_test/robots/welcome_page_robot.dart @@ -0,0 +1,44 @@ +import 'dart:io'; + +import 'package:cake_wallet/src/screens/welcome/welcome_page.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../helpers/components/common_checks.dart'; + +class WelcomePageRobot { + WelcomePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isWelcomePage() async { + await commonTestCases.isSpecificPage(); + } + + void confirmActionButtonsDisplay() { + final createNewWalletButton = find.byKey(ValueKey('welcome_page_create_new_wallet_button_key')); + + final restoreWalletButton = find.byKey(ValueKey('welcome_page_restore_wallet_button_key')); + + expect(createNewWalletButton, findsOneWidget); + expect(restoreWalletButton, findsOneWidget); + } + + Future navigateToCreateNewWalletPage() async { + tester.printToConsole('Routing to create new wallet page'); + await commonTestCases.tapItemByKey('welcome_page_create_new_wallet_button_key'); + commonTestCases.defaultSleepTime(); + } + + Future navigateToRestoreWalletPage() async { + tester.printToConsole('Routing to restore wallet page'); + await commonTestCases.tapItemByKey('welcome_page_restore_wallet_button_key'); + commonTestCases.defaultSleepTime(); + } + + Future backAndVerify() async { + await commonTestCases.goBack(); + await isWelcomePage(); + } +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 170db929ce..f78e1f89d7 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -120,6 +120,8 @@ PODS: - Toast - in_app_review (0.2.0): - Flutter + - integration_test (0.0.1): + - Flutter - MTBBarcodeScanner (5.0.11) - OrderedSet (5.0.0) - package_info (0.0.1): @@ -182,6 +184,7 @@ DEPENDENCIES: - flutter_secure_storage (from `.symlinks/plugins/flutter_secure_storage/ios`) - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`) + - integration_test (from `.symlinks/plugins/integration_test/ios`) - package_info (from `.symlinks/plugins/package_info/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) @@ -246,6 +249,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/fluttertoast/ios" in_app_review: :path: ".symlinks/plugins/in_app_review/ios" + integration_test: + :path: ".symlinks/plugins/integration_test/ios" package_info: :path: ".symlinks/plugins/package_info/ios" package_info_plus: @@ -294,6 +299,7 @@ SPEC CHECKSUMS: flutter_secure_storage: 23fc622d89d073675f2eaa109381aefbcf5a49be fluttertoast: 48c57db1b71b0ce9e6bba9f31c940ff4b001293c in_app_review: 318597b3a06c22bb46dc454d56828c85f444f99d + integration_test: 13825b8a9334a850581300559b8839134b124670 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 @@ -305,7 +311,7 @@ SPEC CHECKSUMS: reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c SDWebImage: a3ba0b8faac7228c3c8eadd1a55c9c9fe5e16457 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 - share_plus: 056a1e8ac890df3e33cb503afffaf1e9b4fbae68 + share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 SwiftProtobuf: 407a385e97fd206c4fbe880cc84123989167e0d1 diff --git a/ios/Runner.xcodeproj/project.pbxproj b/ios/Runner.xcodeproj/project.pbxproj index 8ed46a0288..2196bc289a 100644 --- a/ios/Runner.xcodeproj/project.pbxproj +++ b/ios/Runner.xcodeproj/project.pbxproj @@ -164,7 +164,7 @@ 97C146E61CF9000F007C117D /* Project object */ = { isa = PBXProject; attributes = { - LastUpgradeCheck = 1300; + LastUpgradeCheck = 1510; ORGANIZATIONNAME = ""; TargetAttributes = { 97C146ED1CF9000F007C117D = { @@ -355,7 +355,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; @@ -449,7 +449,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = YES; ONLY_ACTIVE_ARCH = YES; SDKROOT = iphoneos; @@ -499,7 +499,7 @@ GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; GCC_WARN_UNUSED_FUNCTION = YES; GCC_WARN_UNUSED_VARIABLE = YES; - IPHONEOS_DEPLOYMENT_TARGET = 11.0; + IPHONEOS_DEPLOYMENT_TARGET = 12.0; MTL_ENABLE_DEBUG_INFO = NO; SDKROOT = iphoneos; SUPPORTED_PLATFORMS = iphoneos; diff --git a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme index c87d15a335..5e31d3d342 100644 --- a/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme +++ b/ios/Runner.xcodeproj/xcshareddata/xcschemes/Runner.xcscheme @@ -1,6 +1,6 @@ with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { - return Observer(builder: (BuildContext context) { - final appStore = getIt.get(); - final authService = getIt.get(); - final linkViewModel = getIt.get(); - final settingsStore = appStore.settingsStore; - final statusBarColor = Colors.transparent; - final authenticationStore = getIt.get(); - final initialRoute = authenticationStore.state == AuthenticationState.uninitialized - ? Routes.disclaimer - : Routes.login; - final currentTheme = settingsStore.currentTheme; - final statusBarBrightness = - currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; - final statusBarIconBrightness = - currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; - SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( - statusBarColor: statusBarColor, - statusBarBrightness: statusBarBrightness, - statusBarIconBrightness: statusBarIconBrightness)); - - return Root( + return Observer( + builder: (BuildContext context) { + final appStore = getIt.get(); + final authService = getIt.get(); + final linkViewModel = getIt.get(); + final settingsStore = appStore.settingsStore; + final statusBarColor = Colors.transparent; + final authenticationStore = getIt.get(); + final initialRoute = authenticationStore.state == AuthenticationState.uninitialized + ? Routes.disclaimer + : Routes.login; + + final currentTheme = settingsStore.currentTheme; + final statusBarBrightness = + currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; + final statusBarIconBrightness = + currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; + + SystemChrome.setSystemUIOverlayStyle( + SystemUiOverlayStyle( + statusBarColor: statusBarColor, + statusBarBrightness: statusBarBrightness, + statusBarIconBrightness: statusBarIconBrightness, + ), + ); + + return Root( key: rootKey, appStore: appStore, authenticationStore: authenticationStore, @@ -297,8 +303,10 @@ class AppState extends State with SingleTickerProviderStateMixin { onGenerateRoute: (settings) => Router.createRoute(settings), initialRoute: initialRoute, home: _Home(), - )); - }); + ), + ); + }, + ); } } diff --git a/lib/src/screens/disclaimer/disclaimer_page.dart b/lib/src/screens/disclaimer/disclaimer_page.dart index 3805f6240e..c6d52968b8 100644 --- a/lib/src/screens/disclaimer/disclaimer_page.dart +++ b/lib/src/screens/disclaimer/disclaimer_page.dart @@ -23,12 +23,10 @@ class DisclaimerPage extends BasePage { String get title => 'Terms of Use'; @override - Widget? leading(BuildContext context) => - isReadOnly ? super.leading(context) : null; + Widget? leading(BuildContext context) => isReadOnly ? super.leading(context) : null; @override - Widget body(BuildContext context) => - DisclaimerPageBody(isReadOnly: isReadOnly); + Widget body(BuildContext context) => DisclaimerPageBody(isReadOnly: isReadOnly); } class DisclaimerPageBody extends StatefulWidget { @@ -52,9 +50,7 @@ class DisclaimerBodyState extends State { Future getFileLines() async { _fileText = await rootBundle.loadString( - isMoneroOnly - ? 'assets/text/Monerocom_Terms_of_Use.txt' - : 'assets/text/Terms_of_Use.txt' ); + isMoneroOnly ? 'assets/text/Monerocom_Terms_of_Use.txt' : 'assets/text/Terms_of_Use.txt'); setState(() {}); } @@ -89,7 +85,8 @@ class DisclaimerBodyState extends State { style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, - color: Theme.of(context).extension()!.titleColor), + color: + Theme.of(context).extension()!.titleColor), ), ) ], @@ -106,7 +103,8 @@ class DisclaimerBodyState extends State { style: TextStyle( fontSize: 12.0, fontWeight: FontWeight.bold, - color: Theme.of(context).extension()!.titleColor), + color: + Theme.of(context).extension()!.titleColor), ), ) ], @@ -139,7 +137,8 @@ class DisclaimerBodyState extends State { style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.bold, - color: Theme.of(context).extension()!.titleColor), + color: + Theme.of(context).extension()!.titleColor), ), ) ], @@ -182,10 +181,7 @@ class DisclaimerBodyState extends State { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - Theme.of(context) - .colorScheme - .background - .withOpacity(0.0), + Theme.of(context).colorScheme.background.withOpacity(0.0), Theme.of(context).colorScheme.background, ], begin: FractionalOffset.topCenter, @@ -204,9 +200,10 @@ class DisclaimerBodyState extends State { children: [ Expanded( child: Container( - padding: EdgeInsets.only( - left: 24.0, top: 10.0, right: 24.0, bottom: 10.0), + padding: + EdgeInsets.only(left: 24.0, top: 10.0, right: 24.0, bottom: 10.0), child: InkWell( + key: ValueKey('disclaimer_check_key'), onTap: () { setState(() { _checked = !_checked; @@ -223,13 +220,15 @@ class DisclaimerBodyState extends State { ), decoration: BoxDecoration( border: Border.all( - color: Theme.of(context).extension()!.secondaryTextColor, + color: Theme.of(context) + .extension()! + .secondaryTextColor, width: 1.0), - borderRadius: BorderRadius.all( - Radius.circular(8.0)), + borderRadius: BorderRadius.all(Radius.circular(8.0)), color: Theme.of(context).colorScheme.background), child: _checked ? Icon( + key: ValueKey('disclaimer_check_icon_key'), Icons.check, color: Colors.blue, size: 20.0, @@ -241,7 +240,8 @@ class DisclaimerBodyState extends State { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14.0, - color: Theme.of(context).extension()!.titleColor), + color: + Theme.of(context).extension()!.titleColor), ) ], ), @@ -250,16 +250,19 @@ class DisclaimerBodyState extends State { ], ), Container( - padding: - EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0), + padding: EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0), child: PrimaryButton( + key: ValueKey('disclaimer_accept_button_key'), onPressed: _checked - ? () => Navigator.of(context) - .popAndPushNamed(Routes.welcome) + ? () => Navigator.of(context).popAndPushNamed(Routes.welcome) : null, text: 'Accept', - color: Theme.of(context).extension()!.createNewWalletButtonBackgroundColor, - textColor: Theme.of(context).extension()!.restoreWalletButtonTextColor), + color: Theme.of(context) + .extension()! + .createNewWalletButtonBackgroundColor, + textColor: Theme.of(context) + .extension()! + .restoreWalletButtonTextColor), ), ], ], diff --git a/lib/src/screens/pin_code/pin_code_widget.dart b/lib/src/screens/pin_code/pin_code_widget.dart index 36328aee2f..85f0be3236 100644 --- a/lib/src/screens/pin_code/pin_code_widget.dart +++ b/lib/src/screens/pin_code/pin_code_widget.dart @@ -144,8 +144,7 @@ class PinCodeState extends State { style: TextStyle( fontSize: 20, fontWeight: FontWeight.w500, - color: - Theme.of(context).extension()!.titleColor)), + color: Theme.of(context).extension()!.titleColor)), Spacer(flex: 3), Container( width: 180, @@ -162,7 +161,9 @@ class PinCodeState extends State { shape: BoxShape.circle, color: isFilled ? Theme.of(context).extension()!.titleColor - : Theme.of(context).extension()!.indicatorsColor + : Theme.of(context) + .extension()! + .indicatorsColor .withOpacity(0.25), )); }), @@ -203,56 +204,67 @@ class PinCodeState extends State { crossAxisCount: 3, childAspectRatio: _aspectRatio, physics: const NeverScrollableScrollPhysics(), - children: List.generate(12, (index) { - const double marginRight = 15; - const double marginLeft = 15; + children: List.generate( + 12, + (index) { + const double marginRight = 15; + const double marginLeft = 15; + + if (index == 9) { + // Empty container + return Container( + margin: EdgeInsets.only(left: marginLeft, right: marginRight), + ); + } else if (index == 10) { + index = 0; + } else if (index == 11) { + return MergeSemantics( + child: Container( + margin: + EdgeInsets.only(left: marginLeft, right: marginRight), + child: Semantics( + label: S.of(context).delete, + button: true, + onTap: () => _pop(), + child: TextButton( + onPressed: () => _pop(), + style: TextButton.styleFrom( + backgroundColor: + Theme.of(context).colorScheme.background, + shape: CircleBorder(), + ), + child: deleteIconImage, + ), + ), + ), + ); + } else { + index++; + } - if (index == 9) { - // Empty container return Container( margin: EdgeInsets.only(left: marginLeft, right: marginRight), - ); - } else if (index == 10) { - index = 0; - } else if (index == 11) { - return MergeSemantics( - child: Container( - margin: EdgeInsets.only(left: marginLeft, right: marginRight), - child: Semantics( - label: S.of(context).delete, - button: true, - onTap: () => _pop(), - child: TextButton( - onPressed: () => _pop(), - style: TextButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.background, - shape: CircleBorder(), - ), - child: deleteIconImage, + child: TextButton( + key: ValueKey('pin_code_button_${index}_key'), + onPressed: () => _push(index), + style: TextButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.background, + shape: CircleBorder(), + ), + child: Text( + '$index', + style: TextStyle( + fontSize: 30.0, + fontWeight: FontWeight.w600, + color: Theme.of(context) + .extension()! + .titleColor, ), ), ), ); - } else { - index++; - } - - return Container( - margin: EdgeInsets.only(left: marginLeft, right: marginRight), - child: TextButton( - onPressed: () => _push(index), - style: TextButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.background, - shape: CircleBorder(), - ), - child: Text('$index', - style: TextStyle( - fontSize: 30.0, - fontWeight: FontWeight.w600, - color: Theme.of(context).extension()!.titleColor)), - ), - ); - }), + }, + ), ), ) : null, diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart index 2142fdf9bc..83fffb3317 100644 --- a/lib/src/screens/welcome/welcome_page.dart +++ b/lib/src/screens/welcome/welcome_page.dart @@ -42,122 +42,130 @@ class WelcomePage extends BasePage { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - resizeToAvoidBottomInset: false, - body: body(context)); + backgroundColor: Theme.of(context).colorScheme.background, + resizeToAvoidBottomInset: false, + body: body(context), + ); } @override Widget body(BuildContext context) { - final welcomeImage = currentTheme.type == ThemeType.dark - ? welcomeImageDark - : welcomeImageLight; + final welcomeImage = currentTheme.type == ThemeType.dark ? welcomeImageDark : welcomeImageLight; - final newWalletImage = Image.asset('assets/images/new_wallet.png', - height: 12, - width: 12, - color: Theme.of(context).extension()!.restoreWalletButtonTextColor); - final restoreWalletImage = Image.asset('assets/images/restore_wallet.png', - height: 12, - - width: 12, - color: Theme.of(context).extension()!.titleColor); + final newWalletImage = Image.asset( + 'assets/images/new_wallet.png', + height: 12, + width: 12, + color: Theme.of(context).extension()!.restoreWalletButtonTextColor, + ); + + final restoreWalletImage = Image.asset( + 'assets/images/restore_wallet.png', + height: 12, + width: 12, + color: Theme.of(context).extension()!.titleColor, + ); return WillPopScope( - onWillPop: () async => false, - child: Container( - alignment: Alignment.center, - padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24), - child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, + onWillPop: () async => false, + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24), + child: ConstrainedBox( + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + Column( children: [ - Column( - children: [ - AspectRatio( - aspectRatio: aspectRatioImage, - child: FittedBox( - child: welcomeImage, fit: BoxFit.contain), - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: Text( - S.of(context).welcome, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.hintTextColor, - ), - textAlign: TextAlign.center, - ), - ), - Padding( - padding: EdgeInsets.only(top: 5), - child: Text( - appTitle(context), - style: TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Theme.of(context).extension()!.titleColor, - ), - textAlign: TextAlign.center, - ), - ), - Padding( - padding: EdgeInsets.only(top: 5), - child: Text( - appDescription(context), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.hintTextColor, - ), - textAlign: TextAlign.center, - ), + AspectRatio( + aspectRatio: aspectRatioImage, + child: FittedBox(child: welcomeImage, fit: BoxFit.contain), + ), + Padding( + padding: EdgeInsets.only(top: 24), + child: Text( + S.of(context).welcome, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.hintTextColor, ), - ], + textAlign: TextAlign.center, + ), ), - Column( - children: [ - Text( - S.of(context).please_make_selection, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.hintTextColor, - ), - textAlign: TextAlign.center, + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + appTitle(context), + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Theme.of(context).extension()!.titleColor, ), - Padding( - padding: EdgeInsets.only(top: 24), - child: PrimaryImageButton( - onPressed: () => Navigator.pushNamed( - context, Routes.newWalletFromWelcome), - image: newWalletImage, - text: S.of(context).create_new, - color: Theme.of(context).extension()!.createNewWalletButtonBackgroundColor, - textColor: Theme.of(context).extension()!.restoreWalletButtonTextColor, - ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + appDescription(context), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.hintTextColor, ), - Padding( - padding: EdgeInsets.only(top: 10), - child: PrimaryImageButton( - onPressed: () { - Navigator.pushNamed( - context, Routes.restoreOptions, - arguments: true); - }, - image: restoreWalletImage, - text: S.of(context).restore_wallet, - color: Theme.of(context).cardColor, - textColor: Theme.of(context).extension()!.titleColor), - ) - ], - ) + textAlign: TextAlign.center, + ), + ), ], ), - ))); + Column( + children: [ + Text( + S.of(context).please_make_selection, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.hintTextColor, + ), + textAlign: TextAlign.center, + ), + Padding( + padding: EdgeInsets.only(top: 24), + child: PrimaryImageButton( + key: ValueKey('welcome_page_create_new_wallet_button_key'), + onPressed: () => Navigator.pushNamed(context, Routes.newWalletFromWelcome), + image: newWalletImage, + text: S.of(context).create_new, + color: Theme.of(context) + .extension()! + .createNewWalletButtonBackgroundColor, + textColor: Theme.of(context) + .extension()! + .restoreWalletButtonTextColor, + ), + ), + Padding( + padding: EdgeInsets.only(top: 10), + child: PrimaryImageButton( + key: ValueKey('welcome_page_restore_wallet_button_key'), + onPressed: () { + Navigator.pushNamed(context, Routes.restoreOptions, arguments: true); + }, + image: restoreWalletImage, + text: S.of(context).restore_wallet, + color: Theme.of(context).cardColor, + textColor: Theme.of(context).extension()!.titleColor, + ), + ) + ], + ) + ], + ), + ), + ), + ); } } diff --git a/lib/src/widgets/primary_button.dart b/lib/src/widgets/primary_button.dart index 5f6b50f8bd..b0a16923f5 100644 --- a/lib/src/widgets/primary_button.dart +++ b/lib/src/widgets/primary_button.dart @@ -4,15 +4,17 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; class PrimaryButton extends StatelessWidget { - const PrimaryButton( - {required this.text, - required this.color, - required this.textColor, - this.onPressed, - this.isDisabled = false, - this.isDottedBorder = false, - this.borderColor = Colors.black, - this.onDisabledPressed}); + const PrimaryButton({ + required this.text, + required this.color, + required this.textColor, + this.onPressed, + this.isDisabled = false, + this.isDottedBorder = false, + this.borderColor = Colors.black, + this.onDisabledPressed, + super.key, + }); final VoidCallback? onPressed; final VoidCallback? onDisabledPressed; @@ -28,27 +30,31 @@ class PrimaryButton extends StatelessWidget { final content = ConstrainedBox( constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: isDisabled - ? (onDisabledPressed != null ? onDisabledPressed : null) : onPressed, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), - ), - ), - overlayColor: MaterialStateProperty.all(Colors.transparent)), - child: Text(text, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.w600, - color: isDisabled - ? textColor.withOpacity(0.5) - : textColor)), - )), + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: + isDisabled ? (onDisabledPressed != null ? onDisabledPressed : null) : onPressed, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), + ), + ), + overlayColor: MaterialStateProperty.all(Colors.transparent), + ), + child: Text( + text, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.w600, + color: isDisabled ? textColor.withOpacity(0.5) : textColor, + ), + ), + ), + ), ); return isDottedBorder @@ -58,19 +64,21 @@ class PrimaryButton extends StatelessWidget { color: borderColor, strokeWidth: 2, radius: Radius.circular(26), - child: content) + child: content, + ) : content; } } class LoadingPrimaryButton extends StatelessWidget { - const LoadingPrimaryButton( - {required this.onPressed, - required this.text, - required this.color, - required this.textColor, - this.isDisabled = false, - this.isLoading = false}); + const LoadingPrimaryButton({ + required this.onPressed, + required this.text, + required this.color, + required this.textColor, + this.isDisabled = false, + this.isLoading = false, + }); final VoidCallback onPressed; final Color color; @@ -82,30 +90,34 @@ class LoadingPrimaryButton extends StatelessWidget { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + constraints: BoxConstraints( + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint, + ), child: SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: (isLoading || isDisabled) ? null : onPressed, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), - ), - )), - - child: isLoading - ? CupertinoActivityIndicator(animating: true) - : Text(text, - style: TextStyle( + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: (isLoading || isDisabled) ? null : onPressed, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), + ), + ), + ), + child: isLoading + ? CupertinoActivityIndicator(animating: true) + : Text( + text, + style: TextStyle( fontSize: 15.0, fontWeight: FontWeight.w600, - color: isDisabled - ? textColor.withOpacity(0.5) - : textColor - )), - )), + color: isDisabled ? textColor.withOpacity(0.5) : textColor, + ), + ), + ), + ), ); } } @@ -121,7 +133,7 @@ class PrimaryIconButton extends StatelessWidget { required this.iconBackgroundColor, required this.textColor, this.mainAxisAlignment = MainAxisAlignment.start, - this.radius = 26 + this.radius = 26, }); final VoidCallback onPressed; @@ -138,58 +150,68 @@ class PrimaryIconButton extends StatelessWidget { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + constraints: BoxConstraints( + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint, + ), child: SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: onPressed, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(radius), - ), - )), - child: Stack( - children: [ - Row( - mainAxisAlignment: mainAxisAlignment, - children: [ - Container( - width: 26.0, - height: 52.0, - decoration: BoxDecoration( - shape: BoxShape.circle, color: iconBackgroundColor), - child: Center( - child: Icon(iconData, color: iconColor, size: 22.0) + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: onPressed, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radius), + ), + ), + ), + child: Stack( + children: [ + Row( + mainAxisAlignment: mainAxisAlignment, + children: [ + Container( + width: 26.0, + height: 52.0, + decoration: BoxDecoration(shape: BoxShape.circle, color: iconBackgroundColor), + child: Center( + child: Icon( + iconData, + color: iconColor, + size: 22.0, ), ), - ], - ), - Container( - height: 52.0, - child: Center( - child: Text(text, - style: TextStyle( - fontSize: 16.0, - color: textColor)), ), - ) - ], - ), - )), + ], + ), + Container( + height: 52.0, + child: Center( + child: Text( + text, + style: TextStyle(fontSize: 16.0, color: textColor), + ), + ), + ) + ], + ), + ), + ), ); } } class PrimaryImageButton extends StatelessWidget { - const PrimaryImageButton( - {required this.onPressed, - required this.image, - required this.text, - required this.color, - required this.textColor, - this.borderColor = Colors.transparent}); + const PrimaryImageButton({ + required this.onPressed, + required this.image, + required this.text, + required this.color, + required this.textColor, + this.borderColor = Colors.transparent, + super.key, + }); final VoidCallback onPressed; final Image image; @@ -201,36 +223,41 @@ class PrimaryImageButton extends StatelessWidget { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + constraints: BoxConstraints( + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint, + ), child: SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: onPressed, - style: ButtonStyle(backgroundColor: MaterialStateProperty.all(color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), - ), - )), - child:Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - image, - SizedBox(width: 15), - Text( - text, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: textColor - ), - ) - ], + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: onPressed, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), ), - ) - )), + ), + ), + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + image, + SizedBox(width: 15), + Text( + text, + style: TextStyle( + fontSize: 15, + fontWeight: FontWeight.w600, + color: textColor, + ), + ) + ], + ), + ), + ), + ), ); } } diff --git a/macos/Flutter/GeneratedPluginRegistrant.swift b/macos/Flutter/GeneratedPluginRegistrant.swift index 51f61d9e38..e7e812f90e 100644 --- a/macos/Flutter/GeneratedPluginRegistrant.swift +++ b/macos/Flutter/GeneratedPluginRegistrant.swift @@ -16,7 +16,7 @@ import in_app_review import package_info import package_info_plus import path_provider_foundation -import share_plus_macos +import share_plus import shared_preferences_foundation import url_launcher_macos import wakelock_plus diff --git a/pubspec_base.yaml b/pubspec_base.yaml index 47407f8336..8bb4d45c74 100644 --- a/pubspec_base.yaml +++ b/pubspec_base.yaml @@ -21,7 +21,7 @@ dependencies: mobx: ^2.1.4 flutter_mobx: ^2.0.6+5 flutter_slidable: ^3.0.1 - share_plus: ^4.0.10 + share_plus: ^7.2.2 # date_range_picker: ^1.0.6 #https://api.flutter.dev/flutter/material/showDateRangePicker.html dio: ^4.0.6 @@ -112,11 +112,14 @@ dependencies: dev_dependencies: flutter_test: sdk: flutter + integration_test: + sdk: flutter + mocktail: ^1.0.4 build_runner: ^2.3.3 logging: ^1.2.0 mobx_codegen: ^2.1.1 build_resolvers: ^2.0.9 - hive_generator: ^1.1.3 + hive_generator: ^2.0.1 # flutter_launcher_icons: ^0.11.0 # check flutter_launcher_icons for usage pedantic: ^1.8.0 diff --git a/test_driver/integration_test.dart b/test_driver/integration_test.dart new file mode 100644 index 0000000000..533cfb6e7b --- /dev/null +++ b/test_driver/integration_test.dart @@ -0,0 +1,34 @@ +import 'dart:convert'; + +import 'package:integration_test/integration_test_driver.dart'; +import 'package:path/path.dart' as path; + +import 'package:flutter_driver/flutter_driver.dart'; + +Future main() async { + integrationDriver( + responseDataCallback: (Map? data) async { + await fs.directory(_destinationDirectory).create(recursive: true); + + final file = fs.file( + path.join( + _destinationDirectory, + '$_testOutputFilename.json', + ), + ); + + if (data != null) { + final resultString = _encodeJson(data); + await file.writeAsString(resultString); + } + }, + ); +} + +String _encodeJson(Map jsonObject) { + return _prettyEncoder.convert(jsonObject); +} + +const _prettyEncoder = JsonEncoder.withIndent(' '); +const _testOutputFilename = 'integration_response_data'; +const _destinationDirectory = 'integration_test'; \ No newline at end of file From 767ea3718e96084acc1e8b068cce439bb9e51d14 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Mon, 24 Jun 2024 13:03:28 +0100 Subject: [PATCH 02/69] feat: Integration test flow from start to restoring a wallet successfully done --- integration_test/app_test.dart | 98 +++++-- .../components/common_checks.dart | 4 +- .../robots/disclaimer_page_robot.dart | 2 +- .../robots/new_wallet_type_page_robot.dart | 59 ++++ .../restore_from_seed_or_key_robot.dart | 82 ++++++ .../robots/restore_options_page_robot.dart | 45 +++ .../robots/setup_pin_code_robot.dart | 34 ++- .../robots/welcome_page_robot.dart | 2 +- lib/main.dart | 54 ++-- .../screens/disclaimer/disclaimer_page.dart | 52 ++-- .../new_wallet/new_wallet_type_page.dart | 3 + .../new_wallet/widgets/select_button.dart | 1 + lib/src/screens/pin_code/pin_code_widget.dart | 105 +++---- .../screens/restore/restore_options_page.dart | 20 +- .../wallet_restore_from_seed_form.dart | 2 + .../screens/restore/wallet_restore_page.dart | 2 + .../setup_pin_code/setup_pin_code.dart | 1 + lib/src/screens/welcome/welcome_page.dart | 216 +++++++------- lib/src/widgets/alert_with_one_action.dart | 5 +- lib/src/widgets/base_text_form_field.dart | 3 +- lib/src/widgets/option_tile.dart | 3 +- lib/src/widgets/primary_button.dart | 271 ++++++++---------- .../scollable_with_bottom_section.dart | 3 + lib/src/widgets/seed_widget.dart | 7 +- 24 files changed, 654 insertions(+), 420 deletions(-) rename integration_test/{helpers => }/components/common_checks.dart (89%) create mode 100644 integration_test/robots/new_wallet_type_page_robot.dart create mode 100644 integration_test/robots/restore_from_seed_or_key_robot.dart create mode 100644 integration_test/robots/restore_options_page_robot.dart diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index b3a02f176f..252fc2ed58 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -1,9 +1,16 @@ +import 'dart:io'; + import 'package:cake_wallet/main.dart' as app; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; import 'robots/disclaimer_page_robot.dart'; +import 'robots/new_wallet_type_page_robot.dart'; +import 'robots/restore_from_seed_or_key_robot.dart'; +import 'robots/restore_options_page_robot.dart'; import 'robots/setup_pin_code_robot.dart'; import 'robots/welcome_page_robot.dart'; @@ -18,60 +25,117 @@ Future restoreFlutterError() async { void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + DisclaimerPageRobot disclaimerPageRobot; WelcomePageRobot welcomePageRobot; SetupPinCodeRobot setupPinCodeRobot; + RestoreOptionsPageRobot restoreOptionsPageRobot; + NewWalletTypePageRobot newWalletTypePageRobot; + RestoreFromSeedOrKeysPageRobot restoreFromSeedOrKeysPageRobot; group('Startup Test', () { - testWidgets('Unauthenticated Startup flow', (tester) async { + testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL', + (tester) async { disclaimerPageRobot = DisclaimerPageRobot(tester); welcomePageRobot = WelcomePageRobot(tester); setupPinCodeRobot = SetupPinCodeRobot(tester); + restoreOptionsPageRobot = RestoreOptionsPageRobot(tester); + newWalletTypePageRobot = NewWalletTypePageRobot(tester); + restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester); await app.main(); await tester.pumpAndSettle(); // --------- Disclaimer Page ------------ // Confirm initial defaults - await disclaimerPageRobot.isDisclaimerPage(); - disclaimerPageRobot.hasCheckIcon(false); - disclaimerPageRobot.hasDisclaimerCheckbox(); + // await disclaimerPageRobot.isDisclaimerPage(); + // disclaimerPageRobot.hasCheckIcon(false); + // disclaimerPageRobot.hasDisclaimerCheckbox(); // Tap checkbox to accept disclaimer await disclaimerPageRobot.tapDisclaimerCheckbox(); // Confirm that page has been updated with the check mark icon in checkbox - disclaimerPageRobot.hasCheckIcon(true); + // disclaimerPageRobot.hasCheckIcon(true); // Tap accept button await disclaimerPageRobot.tapAcceptButton(); tester.printToConsole('Routing to Welcome Page'); // --------- Welcome Page --------------- - // Confirm initial defaults - Widgets to be displayed etc - await welcomePageRobot.isWelcomePage(); - welcomePageRobot.confirmActionButtonsDisplay(); + // Confirm initial defaults + // await welcomePageRobot.isWelcomePage(); + // welcomePageRobot.confirmActionButtonsDisplay(); // Confirm routing to Create Wallet Page works - await welcomePageRobot.navigateToCreateNewWalletPage(); - await welcomePageRobot.backAndVerify(); + // await welcomePageRobot.navigateToCreateNewWalletPage(); + // await welcomePageRobot.backAndVerify(); // Confirm routing to Restore Wallet Page works - await welcomePageRobot.navigateToRestoreWalletPage(); - await welcomePageRobot.backAndVerify(); + // await welcomePageRobot.navigateToRestoreWalletPage(); + // await welcomePageRobot.backAndVerify(); // Route to restore wallet to continue flow await welcomePageRobot.navigateToRestoreWalletPage(); tester.printToConsole('Routing to Restore Wallet Page'); + // ----------- Restore Options Page ----------- + // Confirm initial defaults + // await restoreOptionsPageRobot.isRestoreOptionsPage(); + // restoreOptionsPageRobot.hasRestoreOptionsButton(); + + // Confirm routing to Restore from seeds Page works + // await restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); + // await restoreOptionsPageRobot.backAndVerify(); + + // Confirm routing to Restore from backup Page works + // await restoreOptionsPageRobot.navigateToRestoreFromBackupPage(); + // await restoreOptionsPageRobot.backAndVerify(); + + // Confirm routing to Restore from hardware wallet Page works + // await restoreOptionsPageRobot.navigateToRestoreFromHardwareWalletPage(); + // await restoreOptionsPageRobot.backAndVerify(); + + // Route to restore from seeds page to continue flow + await restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); + // ----------- SetupPinCode Page ------------- // Confirm initial defaults - Widgets to be displayed etc - await setupPinCodeRobot.isSetupPinCodePage(); - setupPinCodeRobot.hasPinCodeWidget(); - setupPinCodeRobot.hasTitle(); - setupPinCodeRobot.hasNumberButtonsVisible(); + // await setupPinCodeRobot.isSetupPinCodePage(); + // setupPinCodeRobot.hasPinCodeWidget(); + // setupPinCodeRobot.hasTitle(); + // setupPinCodeRobot.hasNumberButtonsVisible(); + + await setupPinCodeRobot.enterPinCode(true); + await setupPinCodeRobot.enterPinCode(false); + await setupPinCodeRobot.tapSuccessButton(); + + // ----------- NewWalletType Page ------------- + // Confirm initial defaults - Widgets to be displayed etc + // await newWalletTypePageRobot.isNewWalletTypePage(); + // newWalletTypePageRobot.displaysCorrectTitle(false); + // newWalletTypePageRobot.displaysCorrectImage(ThemeType.dark); + // newWalletTypePageRobot.hasWalletTypeForm(); + + // Confirm scroll behaviour works properly + await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.haven); - await setupPinCodeRobot.enterPinCode(); + // Select a wallet and route to next page + await newWalletTypePageRobot.selectWalletType(WalletType.solana); + await newWalletTypePageRobot.onNextButtonTapped(); + + // ----------- RestoreFromSeedOrKeys Page ------------- + // Confirm initial defaults - Widgets to be displayed etc + // await restoreFromSeedOrKeysPageRobot.isRestoreFromSeedKeyPage(); + // await restoreFromSeedOrKeysPageRobot.confirmViewComponentsDisplayProperlyPerPageView(); + // restoreFromSeedOrKeysPageRobot.confirmRestoreButtonDisplays(); + // restoreFromSeedOrKeysPageRobot.confirmAdvancedSettingButtonDisplays(); + + await restoreFromSeedOrKeysPageRobot.enterWalletNameText('Integrated Testing Wallet'); + await restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore( + 'noble define inflict tackle sweet essence mention bicycle word hard patient ketchup', + ); + await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonTapped(); }); }); } diff --git a/integration_test/helpers/components/common_checks.dart b/integration_test/components/common_checks.dart similarity index 89% rename from integration_test/helpers/components/common_checks.dart rename to integration_test/components/common_checks.dart index 679874e01a..90cdb710de 100644 --- a/integration_test/helpers/components/common_checks.dart +++ b/integration_test/components/common_checks.dart @@ -19,9 +19,9 @@ class CommonTestCases { await tester.pumpAndSettle(); } - void hasText(String text) { + void hasText(String text, {bool hasWidget = true}) { final textWidget = find.text(text); - expect(textWidget, findsOneWidget); + expect(textWidget, hasWidget ? findsOneWidget : findsNothing); } void hasType() { diff --git a/integration_test/robots/disclaimer_page_robot.dart b/integration_test/robots/disclaimer_page_robot.dart index 1f7bfaf816..a102ec8a1b 100644 --- a/integration_test/robots/disclaimer_page_robot.dart +++ b/integration_test/robots/disclaimer_page_robot.dart @@ -2,7 +2,7 @@ import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../helpers/components/common_checks.dart'; +import '../components/common_checks.dart'; class DisclaimerPageRobot { DisclaimerPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); diff --git a/integration_test/robots/new_wallet_type_page_robot.dart b/integration_test/robots/new_wallet_type_page_robot.dart new file mode 100644 index 0000000000..8cc49022a5 --- /dev/null +++ b/integration_test/robots/new_wallet_type_page_robot.dart @@ -0,0 +1,59 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_type_page.dart'; +import 'package:cake_wallet/themes/theme_base.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; + +class NewWalletTypePageRobot { + NewWalletTypePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isNewWalletTypePage() async { + await commonTestCases.isSpecificPage(); + } + + void displaysCorrectTitle(bool isCreate) { + commonTestCases.hasText( + isCreate ? S.current.wallet_list_create_new_wallet : S.current.wallet_list_restore_wallet, + ); + } + + void hasWalletTypeForm() { + commonTestCases.hasType(); + } + + void displaysCorrectImage(ThemeType type) { + final walletTypeImage = Image.asset('assets/images/wallet_type.png').image; + final walletTypeLightImage = Image.asset('assets/images/wallet_type_light.png').image; + + find.image( + type == ThemeType.dark ? walletTypeImage : walletTypeLightImage, + ); + } + + Future findParticularWalletTypeInScrollableList(WalletType type) async { + final scrollableWidget = find.descendant( + of: find.byKey(Key('new_wallet_type_scrollable_key')), + matching: find.byType(Scrollable), + ); + + await tester.scrollUntilVisible( + find.byKey(ValueKey('new_wallet_type_${type.name}_button_key')), + 300, + scrollable: scrollableWidget, + ); + } + + Future selectWalletType(WalletType type) async { + await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key'); + } + + Future onNextButtonTapped() async { + await commonTestCases.tapItemByKey('new_wallet_type_next_button_key'); + } +} diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart new file mode 100644 index 0000000000..e57551f61c --- /dev/null +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -0,0 +1,82 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; +import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; +import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; + +class RestoreFromSeedOrKeysPageRobot { + RestoreFromSeedOrKeysPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isRestoreFromSeedKeyPage() async { + await commonTestCases.isSpecificPage(); + } + + Future confirmViewComponentsDisplayProperlyPerPageView() async { + commonTestCases.hasText(S.current.wallet_name); + commonTestCases.hasText(S.current.enter_seed_phrase); + commonTestCases.hasText(S.current.restore_title_from_seed); + + commonTestCases.hasKey('wallet_restore_from_seed_wallet_name_textfield_key'); + commonTestCases.hasKey('wallet_restore_from_seed_wallet_name_refresh_button_key'); + commonTestCases.hasKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'); + commonTestCases.hasKey('wallet_restore_from_seed_wallet_seeds_textfield_key'); + + commonTestCases.hasText(S.current.private_key, hasWidget: false); + commonTestCases.hasText(S.current.restore_title_from_keys, hasWidget: false); + + await tester.drag(find.byType(PageView), Offset(-300, 0)); + await tester.pumpAndSettle(); + commonTestCases.defaultSleepTime(); + + commonTestCases.hasText(S.current.wallet_name); + commonTestCases.hasText(S.current.private_key); + commonTestCases.hasText(S.current.restore_title_from_keys); + + commonTestCases.hasText(S.current.enter_seed_phrase, hasWidget: false); + commonTestCases.hasText(S.current.restore_title_from_seed, hasWidget: false); + + await tester.drag(find.byType(PageView), Offset(300, 0)); + await tester.pumpAndSettle(); + } + + void confirmRestoreButtonDisplays() { + commonTestCases.hasKey('wallet_restore_seed_or_key_restore_button_key'); + } + + void confirmAdvancedSettingButtonDisplays() { + commonTestCases.hasKey('wallet_restore_advanced_settings_button_key'); + } + + Future enterWalletNameText(String walletName) async { + final walletNameTextField = + find.byKey(ValueKey(('wallet_restore_from_seed_wallet_name_textfield_key'))); + await tester.enterText(walletNameTextField, walletName); + await tester.pumpAndSettle(); + } + + Future selectWalletNameFromAvailableOptions() async { + await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_name_refresh_button_key'); + } + + Future enterSeedPhraseForWalletRestore(String text) async { + ValidatableAnnotatedEditableTextState seedTextState = + await tester.state(find.byType(ValidatableAnnotatedEditableText)); + + seedTextState.widget.controller.text = text; + await tester.pumpAndSettle(); + } + + Future onPasteSeedPhraseButtonTapped() async { + await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'); + } + + Future onRestoreWalletButtonTapped() async { + await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key'); + } +} diff --git a/integration_test/robots/restore_options_page_robot.dart b/integration_test/robots/restore_options_page_robot.dart new file mode 100644 index 0000000000..faa6a24369 --- /dev/null +++ b/integration_test/robots/restore_options_page_robot.dart @@ -0,0 +1,45 @@ +import 'package:cake_wallet/src/screens/restore/restore_options_page.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; + +class RestoreOptionsPageRobot { + RestoreOptionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isRestoreOptionsPage() async { + await commonTestCases.isSpecificPage(); + } + + void hasRestoreOptionsButton() { + commonTestCases.hasKey('restore_options_from_seeds_button_key'); + commonTestCases.hasKey('restore_options_from_backup_button_key'); + commonTestCases.hasKey('restore_options_from_hardware_wallet_button_key'); + commonTestCases.hasKey('restore_options_from_qr_button_key'); + } + + Future navigateToRestoreFromSeedsPage() async { + tester.printToConsole('Routing to restore from seeds page'); + await commonTestCases.tapItemByKey('restore_options_from_seeds_button_key'); + commonTestCases.defaultSleepTime(); + } + + Future navigateToRestoreFromBackupPage() async { + tester.printToConsole('Routing to restore from backup page'); + await commonTestCases.tapItemByKey('restore_options_from_backup_button_key'); + commonTestCases.defaultSleepTime(); + } + + Future navigateToRestoreFromHardwareWalletPage() async { + tester.printToConsole('Routing to restore from hardware wallet page'); + await commonTestCases.tapItemByKey('restore_options_from_hardware_wallet_button_key'); + commonTestCases.defaultSleepTime(); + } + + Future backAndVerify() async { + await commonTestCases.goBack(); + await isRestoreOptionsPage(); + } +} diff --git a/integration_test/robots/setup_pin_code_robot.dart b/integration_test/robots/setup_pin_code_robot.dart index 911ee028f2..b64e63f1f2 100644 --- a/integration_test/robots/setup_pin_code_robot.dart +++ b/integration_test/robots/setup_pin_code_robot.dart @@ -4,7 +4,7 @@ import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../helpers/components/common_checks.dart'; +import '../components/common_checks.dart'; class SetupPinCodeRobot { SetupPinCodeRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -39,18 +39,34 @@ class SetupPinCodeRobot { final button = find.byKey(ValueKey('pin_code_button_${index}_key')); await tester.tap(button); await tester.pumpAndSettle(); - commonTestCases.defaultSleepTime(); } - Future enterPinCode() async { + Future enterPinCode(bool isFirstEntry) async { final PinCodeState pinCodeState = tester.state(find.byType(PinCodeWidget)); - print(pinCodeState.pin); - final codeToUse = [0, 8, 0, 1]; - await codeToUse.map((code) async { - await pushPinButton(code); - }); + tester.printToConsole(pinCodeState.pin); + + await pushPinButton(0); + expect(pinCodeState.pin, '0'); + tester.printToConsole(pinCodeState.pin); + + await pushPinButton(8); + expect(pinCodeState.pin, '08'); + tester.printToConsole(pinCodeState.pin); + + await pushPinButton(0); + expect(pinCodeState.pin, '080'); + tester.printToConsole(pinCodeState.pin); + + await pushPinButton(1); + // the state is cleared once it get's to the last entry + expect(pinCodeState.pin, isFirstEntry ? '' : '0801'); + tester.printToConsole(pinCodeState.pin); + + commonTestCases.defaultSleepTime(); + } + Future tapSuccessButton() async { + await commonTestCases.tapItemByKey('setup_pin_code_success_button_key'); commonTestCases.defaultSleepTime(); - expect(pinCodeState.pin, '0801'); } } diff --git a/integration_test/robots/welcome_page_robot.dart b/integration_test/robots/welcome_page_robot.dart index 43b4969aa4..e61eaa8f4f 100644 --- a/integration_test/robots/welcome_page_robot.dart +++ b/integration_test/robots/welcome_page_robot.dart @@ -4,7 +4,7 @@ import 'package:cake_wallet/src/screens/welcome/welcome_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../helpers/components/common_checks.dart'; +import '../components/common_checks.dart'; class WelcomePageRobot { WelcomePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); diff --git a/lib/main.dart b/lib/main.dart index d7e2637fdf..5bcd9ffdb7 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -259,33 +259,27 @@ class App extends StatefulWidget { class AppState extends State with SingleTickerProviderStateMixin { @override Widget build(BuildContext context) { - return Observer( - builder: (BuildContext context) { - final appStore = getIt.get(); - final authService = getIt.get(); - final linkViewModel = getIt.get(); - final settingsStore = appStore.settingsStore; - final statusBarColor = Colors.transparent; - final authenticationStore = getIt.get(); - final initialRoute = authenticationStore.state == AuthenticationState.uninitialized - ? Routes.disclaimer - : Routes.login; - - final currentTheme = settingsStore.currentTheme; - final statusBarBrightness = - currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; - final statusBarIconBrightness = - currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; - - SystemChrome.setSystemUIOverlayStyle( - SystemUiOverlayStyle( - statusBarColor: statusBarColor, - statusBarBrightness: statusBarBrightness, - statusBarIconBrightness: statusBarIconBrightness, - ), - ); - - return Root( + return Observer(builder: (BuildContext context) { + final appStore = getIt.get(); + final authService = getIt.get(); + final linkViewModel = getIt.get(); + final settingsStore = appStore.settingsStore; + final statusBarColor = Colors.transparent; + final authenticationStore = getIt.get(); + final initialRoute = authenticationStore.state == AuthenticationState.uninitialized + ? Routes.disclaimer + : Routes.login; + final currentTheme = settingsStore.currentTheme; + final statusBarBrightness = + currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; + final statusBarIconBrightness = + currentTheme.type == ThemeType.dark ? Brightness.light : Brightness.dark; + SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle( + statusBarColor: statusBarColor, + statusBarBrightness: statusBarBrightness, + statusBarIconBrightness: statusBarIconBrightness)); + + return Root( key: rootKey, appStore: appStore, authenticationStore: authenticationStore, @@ -303,10 +297,8 @@ class AppState extends State with SingleTickerProviderStateMixin { onGenerateRoute: (settings) => Router.createRoute(settings), initialRoute: initialRoute, home: _Home(), - ), - ); - }, - ); + )); + }); } } diff --git a/lib/src/screens/disclaimer/disclaimer_page.dart b/lib/src/screens/disclaimer/disclaimer_page.dart index c6d52968b8..4ff8269407 100644 --- a/lib/src/screens/disclaimer/disclaimer_page.dart +++ b/lib/src/screens/disclaimer/disclaimer_page.dart @@ -23,10 +23,12 @@ class DisclaimerPage extends BasePage { String get title => 'Terms of Use'; @override - Widget? leading(BuildContext context) => isReadOnly ? super.leading(context) : null; + Widget? leading(BuildContext context) => + isReadOnly ? super.leading(context) : null; @override - Widget body(BuildContext context) => DisclaimerPageBody(isReadOnly: isReadOnly); + Widget body(BuildContext context) => + DisclaimerPageBody(isReadOnly: isReadOnly); } class DisclaimerPageBody extends StatefulWidget { @@ -50,7 +52,9 @@ class DisclaimerBodyState extends State { Future getFileLines() async { _fileText = await rootBundle.loadString( - isMoneroOnly ? 'assets/text/Monerocom_Terms_of_Use.txt' : 'assets/text/Terms_of_Use.txt'); + isMoneroOnly + ? 'assets/text/Monerocom_Terms_of_Use.txt' + : 'assets/text/Terms_of_Use.txt' ); setState(() {}); } @@ -85,8 +89,7 @@ class DisclaimerBodyState extends State { style: TextStyle( fontSize: 20.0, fontWeight: FontWeight.bold, - color: - Theme.of(context).extension()!.titleColor), + color: Theme.of(context).extension()!.titleColor), ), ) ], @@ -103,8 +106,7 @@ class DisclaimerBodyState extends State { style: TextStyle( fontSize: 12.0, fontWeight: FontWeight.bold, - color: - Theme.of(context).extension()!.titleColor), + color: Theme.of(context).extension()!.titleColor), ), ) ], @@ -137,8 +139,7 @@ class DisclaimerBodyState extends State { style: TextStyle( fontSize: 14.0, fontWeight: FontWeight.bold, - color: - Theme.of(context).extension()!.titleColor), + color: Theme.of(context).extension()!.titleColor), ), ) ], @@ -181,7 +182,10 @@ class DisclaimerBodyState extends State { decoration: BoxDecoration( gradient: LinearGradient( colors: [ - Theme.of(context).colorScheme.background.withOpacity(0.0), + Theme.of(context) + .colorScheme + .background + .withOpacity(0.0), Theme.of(context).colorScheme.background, ], begin: FractionalOffset.topCenter, @@ -200,8 +204,8 @@ class DisclaimerBodyState extends State { children: [ Expanded( child: Container( - padding: - EdgeInsets.only(left: 24.0, top: 10.0, right: 24.0, bottom: 10.0), + padding: EdgeInsets.only( + left: 24.0, top: 10.0, right: 24.0, bottom: 10.0), child: InkWell( key: ValueKey('disclaimer_check_key'), onTap: () { @@ -220,11 +224,10 @@ class DisclaimerBodyState extends State { ), decoration: BoxDecoration( border: Border.all( - color: Theme.of(context) - .extension()! - .secondaryTextColor, + color: Theme.of(context).extension()!.secondaryTextColor, width: 1.0), - borderRadius: BorderRadius.all(Radius.circular(8.0)), + borderRadius: BorderRadius.all( + Radius.circular(8.0)), color: Theme.of(context).colorScheme.background), child: _checked ? Icon( @@ -240,8 +243,7 @@ class DisclaimerBodyState extends State { style: TextStyle( fontWeight: FontWeight.bold, fontSize: 14.0, - color: - Theme.of(context).extension()!.titleColor), + color: Theme.of(context).extension()!.titleColor), ) ], ), @@ -250,19 +252,17 @@ class DisclaimerBodyState extends State { ], ), Container( - padding: EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0), + padding: + EdgeInsets.only(left: 24.0, right: 24.0, bottom: 24.0), child: PrimaryButton( key: ValueKey('disclaimer_accept_button_key'), onPressed: _checked - ? () => Navigator.of(context).popAndPushNamed(Routes.welcome) + ? () => Navigator.of(context) + .popAndPushNamed(Routes.welcome) : null, text: 'Accept', - color: Theme.of(context) - .extension()! - .createNewWalletButtonBackgroundColor, - textColor: Theme.of(context) - .extension()! - .restoreWalletButtonTextColor), + color: Theme.of(context).extension()!.createNewWalletButtonBackgroundColor, + textColor: Theme.of(context).extension()!.restoreWalletButtonTextColor), ), ], ], diff --git a/lib/src/screens/new_wallet/new_wallet_type_page.dart b/lib/src/screens/new_wallet/new_wallet_type_page.dart index 65c7bd59b7..23725d7f58 100644 --- a/lib/src/screens/new_wallet/new_wallet_type_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_type_page.dart @@ -124,6 +124,7 @@ class WalletTypeFormState extends State { Expanded( child: ScrollableWithBottomSection( contentPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), + scrollableKey: ValueKey('new_wallet_type_scrollable_key'), content: Column( crossAxisAlignment: CrossAxisAlignment.center, children: [ @@ -131,6 +132,7 @@ class WalletTypeFormState extends State { (type) => Padding( padding: EdgeInsets.only(top: 12), child: SelectButton( + key: ValueKey('new_wallet_type_${type.name}_button_key'), image: Image.asset( walletTypeToCryptoCurrency(type).iconPath ?? '', height: 24, @@ -151,6 +153,7 @@ class WalletTypeFormState extends State { ), bottomSectionPadding: EdgeInsets.only(left: 24, right: 24, bottom: 24), bottomSection: PrimaryButton( + key: ValueKey('new_wallet_type_next_button_key'), onPressed: () => onTypeSelected(), text: S.of(context).seed_language_next, color: Theme.of(context).primaryColor, diff --git a/lib/src/screens/new_wallet/widgets/select_button.dart b/lib/src/screens/new_wallet/widgets/select_button.dart index 2eda77d014..eeb1cf3572 100644 --- a/lib/src/screens/new_wallet/widgets/select_button.dart +++ b/lib/src/screens/new_wallet/widgets/select_button.dart @@ -18,6 +18,7 @@ class SelectButton extends StatelessWidget { this.arrowColor, this.borderColor, this.deviceConnectionTypes, + super.key, }); final Image? image; diff --git a/lib/src/screens/pin_code/pin_code_widget.dart b/lib/src/screens/pin_code/pin_code_widget.dart index 85f0be3236..d39c88cc9e 100644 --- a/lib/src/screens/pin_code/pin_code_widget.dart +++ b/lib/src/screens/pin_code/pin_code_widget.dart @@ -144,7 +144,8 @@ class PinCodeState extends State { style: TextStyle( fontSize: 20, fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.titleColor)), + color: + Theme.of(context).extension()!.titleColor)), Spacer(flex: 3), Container( width: 180, @@ -161,9 +162,7 @@ class PinCodeState extends State { shape: BoxShape.circle, color: isFilled ? Theme.of(context).extension()!.titleColor - : Theme.of(context) - .extension()! - .indicatorsColor + : Theme.of(context).extension()!.indicatorsColor .withOpacity(0.25), )); }), @@ -204,67 +203,57 @@ class PinCodeState extends State { crossAxisCount: 3, childAspectRatio: _aspectRatio, physics: const NeverScrollableScrollPhysics(), - children: List.generate( - 12, - (index) { - const double marginRight = 15; - const double marginLeft = 15; - - if (index == 9) { - // Empty container - return Container( - margin: EdgeInsets.only(left: marginLeft, right: marginRight), - ); - } else if (index == 10) { - index = 0; - } else if (index == 11) { - return MergeSemantics( - child: Container( - margin: - EdgeInsets.only(left: marginLeft, right: marginRight), - child: Semantics( - label: S.of(context).delete, - button: true, - onTap: () => _pop(), - child: TextButton( - onPressed: () => _pop(), - style: TextButton.styleFrom( - backgroundColor: - Theme.of(context).colorScheme.background, - shape: CircleBorder(), - ), - child: deleteIconImage, - ), - ), - ), - ); - } else { - index++; - } + children: List.generate(12, (index) { + const double marginRight = 15; + const double marginLeft = 15; + if (index == 9) { + // Empty container return Container( margin: EdgeInsets.only(left: marginLeft, right: marginRight), - child: TextButton( - key: ValueKey('pin_code_button_${index}_key'), - onPressed: () => _push(index), - style: TextButton.styleFrom( - backgroundColor: Theme.of(context).colorScheme.background, - shape: CircleBorder(), - ), - child: Text( - '$index', - style: TextStyle( - fontSize: 30.0, - fontWeight: FontWeight.w600, - color: Theme.of(context) - .extension()! - .titleColor, + ); + } else if (index == 10) { + index = 0; + } else if (index == 11) { + return MergeSemantics( + child: Container( + margin: EdgeInsets.only(left: marginLeft, right: marginRight), + child: Semantics( + label: S.of(context).delete, + button: true, + onTap: () => _pop(), + child: TextButton( + onPressed: () => _pop(), + style: TextButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.background, + shape: CircleBorder(), + ), + child: deleteIconImage, ), ), ), ); - }, - ), + } else { + index++; + } + + return Container( + margin: EdgeInsets.only(left: marginLeft, right: marginRight), + child: TextButton( + key: ValueKey('pin_code_button_${index}_key'), + onPressed: () => _push(index), + style: TextButton.styleFrom( + backgroundColor: Theme.of(context).colorScheme.background, + shape: CircleBorder(), + ), + child: Text('$index', + style: TextStyle( + fontSize: 30.0, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.titleColor)), + ), + ); + }), ), ) : null, diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index a703c9f9e0..cb5086fe16 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -59,8 +59,12 @@ class RestoreOptionsPage extends BasePage { child: Column( children: [ OptionTile( - onPressed: () => Navigator.pushNamed(context, Routes.restoreWalletFromSeedKeys, - arguments: isNewInstall), + key: ValueKey('restore_options_from_seeds_button_key'), + onPressed: () => Navigator.pushNamed( + context, + Routes.restoreWalletFromSeedKeys, + arguments: isNewInstall, + ), image: imageSeedKeys, title: S.of(context).restore_title_from_seed_keys, description: S.of(context).restore_description_from_seed_keys, @@ -69,6 +73,7 @@ class RestoreOptionsPage extends BasePage { Padding( padding: EdgeInsets.only(top: 24), child: OptionTile( + key: ValueKey('restore_options_from_backup_button_key'), onPressed: () => Navigator.pushNamed(context, Routes.restoreFromBackup), image: imageBackup, title: S.of(context).restore_title_from_backup, @@ -79,6 +84,7 @@ class RestoreOptionsPage extends BasePage { Padding( padding: EdgeInsets.only(top: 24), child: OptionTile( + key: ValueKey('restore_options_from_hardware_wallet_button_key'), onPressed: () => Navigator.pushNamed( context, Routes.restoreWalletFromHardwareWallet, arguments: isNewInstall), @@ -90,10 +96,12 @@ class RestoreOptionsPage extends BasePage { Padding( padding: EdgeInsets.only(top: 24), child: OptionTile( - onPressed: () => _onScanQRCode(context), - image: qrCode, - title: S.of(context).scan_qr_code, - description: S.of(context).cold_or_recover_wallet), + key: ValueKey('restore_options_from_qr_button_key'), + onPressed: () => _onScanQRCode(context), + image: qrCode, + title: S.of(context).scan_qr_code, + description: S.of(context).cold_or_recover_wallet, + ), ) ], ), diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index 288862ce74..7064045b43 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -103,9 +103,11 @@ class WalletRestoreFromSeedFormState extends State { alignment: Alignment.centerRight, children: [ BaseTextFormField( + key: ValueKey('wallet_restore_from_seed_wallet_name_textfield_key'), controller: nameTextEditingController, hintText: S.of(context).wallet_name, suffixIcon: IconButton( + key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'), onPressed: () async { final rName = await generateName(); FocusManager.instance.primaryFocus?.unfocus(); diff --git a/lib/src/screens/restore/wallet_restore_page.dart b/lib/src/screens/restore/wallet_restore_page.dart index 8e6ee09834..3e4922a232 100644 --- a/lib/src/screens/restore/wallet_restore_page.dart +++ b/lib/src/screens/restore/wallet_restore_page.dart @@ -199,6 +199,7 @@ class WalletRestorePage extends BasePage { Observer( builder: (context) { return LoadingPrimaryButton( + key: ValueKey('wallet_restore_seed_or_key_restore_button_key'), onPressed: () async { await _confirmForm(context); }, @@ -216,6 +217,7 @@ class WalletRestorePage extends BasePage { ), const SizedBox(height: 25), GestureDetector( + key: ValueKey('wallet_restore_advanced_settings_button_key'), onTap: () { Navigator.of(context) .pushNamed(Routes.advancedPrivacySettings, arguments: { diff --git a/lib/src/screens/setup_pin_code/setup_pin_code.dart b/lib/src/screens/setup_pin_code/setup_pin_code.dart index 833fd9b602..d78a2df635 100644 --- a/lib/src/screens/setup_pin_code/setup_pin_code.dart +++ b/lib/src/screens/setup_pin_code/setup_pin_code.dart @@ -52,6 +52,7 @@ class SetupPinCodePage extends BasePage { context: context, builder: (BuildContext context) { return AlertWithOneAction( + buttonKey: ValueKey('setup_pin_code_success_button_key'), alertTitle: S.current.setup_pin, alertContent: S.of(context).setup_successful, buttonText: S.of(context).ok, diff --git a/lib/src/screens/welcome/welcome_page.dart b/lib/src/screens/welcome/welcome_page.dart index 83fffb3317..0fea13ceaa 100644 --- a/lib/src/screens/welcome/welcome_page.dart +++ b/lib/src/screens/welcome/welcome_page.dart @@ -42,130 +42,124 @@ class WelcomePage extends BasePage { @override Widget build(BuildContext context) { return Scaffold( - backgroundColor: Theme.of(context).colorScheme.background, - resizeToAvoidBottomInset: false, - body: body(context), - ); + backgroundColor: Theme.of(context).colorScheme.background, + resizeToAvoidBottomInset: false, + body: body(context)); } @override Widget body(BuildContext context) { - final welcomeImage = currentTheme.type == ThemeType.dark ? welcomeImageDark : welcomeImageLight; + final welcomeImage = currentTheme.type == ThemeType.dark + ? welcomeImageDark + : welcomeImageLight; - final newWalletImage = Image.asset( - 'assets/images/new_wallet.png', - height: 12, - width: 12, - color: Theme.of(context).extension()!.restoreWalletButtonTextColor, - ); - - final restoreWalletImage = Image.asset( - 'assets/images/restore_wallet.png', - height: 12, - width: 12, - color: Theme.of(context).extension()!.titleColor, - ); + final newWalletImage = Image.asset('assets/images/new_wallet.png', + height: 12, + width: 12, + color: Theme.of(context).extension()!.restoreWalletButtonTextColor); + final restoreWalletImage = Image.asset('assets/images/restore_wallet.png', + height: 12, + + width: 12, + color: Theme.of(context).extension()!.titleColor); return WillPopScope( - onWillPop: () async => false, - child: Container( - alignment: Alignment.center, - padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24), - child: ConstrainedBox( - constraints: - BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), - child: Column( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Column( + onWillPop: () async => false, + child: Container( + alignment: Alignment.center, + padding: EdgeInsets.only(top: 64, bottom: 24, left: 24, right: 24), + child: ConstrainedBox( + constraints: BoxConstraints( + maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + child: Column( + mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ - AspectRatio( - aspectRatio: aspectRatioImage, - child: FittedBox(child: welcomeImage, fit: BoxFit.contain), - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: Text( - S.of(context).welcome, - style: TextStyle( - fontSize: 18, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.hintTextColor, + Column( + children: [ + AspectRatio( + aspectRatio: aspectRatioImage, + child: FittedBox( + child: welcomeImage, fit: BoxFit.contain), ), - textAlign: TextAlign.center, - ), - ), - Padding( - padding: EdgeInsets.only(top: 5), - child: Text( - appTitle(context), - style: TextStyle( - fontSize: 36, - fontWeight: FontWeight.bold, - color: Theme.of(context).extension()!.titleColor, + Padding( + padding: EdgeInsets.only(top: 24), + child: Text( + S.of(context).welcome, + style: TextStyle( + fontSize: 18, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.hintTextColor, + ), + textAlign: TextAlign.center, + ), ), - textAlign: TextAlign.center, - ), - ), - Padding( - padding: EdgeInsets.only(top: 5), - child: Text( - appDescription(context), - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.hintTextColor, + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + appTitle(context), + style: TextStyle( + fontSize: 36, + fontWeight: FontWeight.bold, + color: Theme.of(context).extension()!.titleColor, + ), + textAlign: TextAlign.center, + ), ), - textAlign: TextAlign.center, - ), - ), - ], - ), - Column( - children: [ - Text( - S.of(context).please_make_selection, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.hintTextColor, - ), - textAlign: TextAlign.center, - ), - Padding( - padding: EdgeInsets.only(top: 24), - child: PrimaryImageButton( - key: ValueKey('welcome_page_create_new_wallet_button_key'), - onPressed: () => Navigator.pushNamed(context, Routes.newWalletFromWelcome), - image: newWalletImage, - text: S.of(context).create_new, - color: Theme.of(context) - .extension()! - .createNewWalletButtonBackgroundColor, - textColor: Theme.of(context) - .extension()! - .restoreWalletButtonTextColor, - ), + Padding( + padding: EdgeInsets.only(top: 5), + child: Text( + appDescription(context), + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.hintTextColor, + ), + textAlign: TextAlign.center, + ), + ), + ], ), - Padding( - padding: EdgeInsets.only(top: 10), - child: PrimaryImageButton( - key: ValueKey('welcome_page_restore_wallet_button_key'), - onPressed: () { - Navigator.pushNamed(context, Routes.restoreOptions, arguments: true); - }, - image: restoreWalletImage, - text: S.of(context).restore_wallet, - color: Theme.of(context).cardColor, - textColor: Theme.of(context).extension()!.titleColor, - ), + Column( + children: [ + Text( + S.of(context).please_make_selection, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.normal, + color: Theme.of(context).extension()!.hintTextColor, + ), + textAlign: TextAlign.center, + ), + Padding( + padding: EdgeInsets.only(top: 24), + child: PrimaryImageButton( + key: ValueKey('welcome_page_create_new_wallet_button_key'), + onPressed: () => Navigator.pushNamed( + context, Routes.newWalletFromWelcome), + image: newWalletImage, + text: S.of(context).create_new, + color: Theme.of(context).extension()!.createNewWalletButtonBackgroundColor, + textColor: Theme.of(context).extension()!.restoreWalletButtonTextColor, + ), + ), + Padding( + padding: EdgeInsets.only(top: 10), + child: PrimaryImageButton( + key: ValueKey('welcome_page_restore_wallet_button_key'), + onPressed: () { + Navigator.pushNamed( + context, Routes.restoreOptions, + arguments: true); + }, + image: restoreWalletImage, + text: S.of(context).restore_wallet, + color: Theme.of(context).cardColor, + textColor: Theme.of(context).extension()!.titleColor), + ) + ], ) ], - ) - ], - ), - ), - ), - ); + ), + ))); } } diff --git a/lib/src/widgets/alert_with_one_action.dart b/lib/src/widgets/alert_with_one_action.dart index 7ad0ac1af0..12ba84d17a 100644 --- a/lib/src/widgets/alert_with_one_action.dart +++ b/lib/src/widgets/alert_with_one_action.dart @@ -9,7 +9,8 @@ class AlertWithOneAction extends BaseAlertDialog { required this.buttonAction, this.alertBarrierDismissible = true, this.headerTitleText, - this.headerImageProfileUrl + this.headerImageProfileUrl, + this.buttonKey, }); final String alertTitle; @@ -19,6 +20,7 @@ class AlertWithOneAction extends BaseAlertDialog { final bool alertBarrierDismissible; final String? headerTitleText; final String? headerImageProfileUrl; + final Key? buttonKey; @override String get titleText => alertTitle; @@ -45,6 +47,7 @@ class AlertWithOneAction extends BaseAlertDialog { child: ButtonTheme( minWidth: double.infinity, child: TextButton( + key: buttonKey, onPressed: buttonAction, // FIX-ME: Style //highlightColor: Colors.transparent, diff --git a/lib/src/widgets/base_text_form_field.dart b/lib/src/widgets/base_text_form_field.dart index 534e6dae23..4648b88cc8 100644 --- a/lib/src/widgets/base_text_form_field.dart +++ b/lib/src/widgets/base_text_form_field.dart @@ -30,7 +30,8 @@ class BaseTextFormField extends StatelessWidget { this.focusNode, this.initialValue, this.onSubmit, - this.borderWidth = 1.0}); + this.borderWidth = 1.0, + super.key}); final TextEditingController? controller; final TextInputType? keyboardType; diff --git a/lib/src/widgets/option_tile.dart b/lib/src/widgets/option_tile.dart index 8b46641fb6..f7811a8884 100644 --- a/lib/src/widgets/option_tile.dart +++ b/lib/src/widgets/option_tile.dart @@ -6,7 +6,8 @@ class OptionTile extends StatelessWidget { {required this.onPressed, required this.image, required this.title, - required this.description}); + required this.description, + super.key}); final VoidCallback onPressed; final Image image; diff --git a/lib/src/widgets/primary_button.dart b/lib/src/widgets/primary_button.dart index b0a16923f5..06bfda157b 100644 --- a/lib/src/widgets/primary_button.dart +++ b/lib/src/widgets/primary_button.dart @@ -30,31 +30,27 @@ class PrimaryButton extends StatelessWidget { final content = ConstrainedBox( constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: - isDisabled ? (onDisabledPressed != null ? onDisabledPressed : null) : onPressed, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), - ), - ), - overlayColor: MaterialStateProperty.all(Colors.transparent), - ), - child: Text( - text, - textAlign: TextAlign.center, - style: TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.w600, - color: isDisabled ? textColor.withOpacity(0.5) : textColor, - ), - ), - ), - ), + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: + isDisabled ? (onDisabledPressed != null ? onDisabledPressed : null) : onPressed, + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), + ), + ), + overlayColor: MaterialStateProperty.all(Colors.transparent)), + child: Text(text, + textAlign: TextAlign.center, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.w600, + color: isDisabled ? textColor.withOpacity(0.5) : textColor)), + )), ); return isDottedBorder @@ -64,8 +60,7 @@ class PrimaryButton extends StatelessWidget { color: borderColor, strokeWidth: 2, radius: Radius.circular(26), - child: content, - ) + child: content) : content; } } @@ -78,6 +73,7 @@ class LoadingPrimaryButton extends StatelessWidget { required this.textColor, this.isDisabled = false, this.isLoading = false, + super.key, }); final VoidCallback onPressed; @@ -90,51 +86,44 @@ class LoadingPrimaryButton extends StatelessWidget { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints( - maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint, - ), + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: (isLoading || isDisabled) ? null : onPressed, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), - ), - ), - ), - child: isLoading - ? CupertinoActivityIndicator(animating: true) - : Text( - text, - style: TextStyle( - fontSize: 15.0, - fontWeight: FontWeight.w600, - color: isDisabled ? textColor.withOpacity(0.5) : textColor, + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: (isLoading || isDisabled) ? null : onPressed, + style: ButtonStyle( + backgroundColor: + MaterialStateProperty.all(isDisabled ? color.withOpacity(0.5) : color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), ), - ), - ), - ), + )), + child: isLoading + ? CupertinoActivityIndicator(animating: true) + : Text(text, + style: TextStyle( + fontSize: 15.0, + fontWeight: FontWeight.w600, + color: isDisabled ? textColor.withOpacity(0.5) : textColor)), + )), ); } } class PrimaryIconButton extends StatelessWidget { - const PrimaryIconButton({ - required this.onPressed, - required this.iconData, - required this.text, - required this.color, - required this.borderColor, - required this.iconColor, - required this.iconBackgroundColor, - required this.textColor, - this.mainAxisAlignment = MainAxisAlignment.start, - this.radius = 26, - }); + const PrimaryIconButton( + {required this.onPressed, + required this.iconData, + required this.text, + required this.color, + required this.borderColor, + required this.iconColor, + required this.iconBackgroundColor, + required this.textColor, + this.mainAxisAlignment = MainAxisAlignment.start, + this.radius = 26, super.key}); final VoidCallback onPressed; final IconData iconData; @@ -150,68 +139,53 @@ class PrimaryIconButton extends StatelessWidget { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints( - maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint, - ), + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: onPressed, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(radius), - ), - ), - ), - child: Stack( - children: [ - Row( - mainAxisAlignment: mainAxisAlignment, - children: [ - Container( - width: 26.0, - height: 52.0, - decoration: BoxDecoration(shape: BoxShape.circle, color: iconBackgroundColor), - child: Center( - child: Icon( - iconData, - color: iconColor, - size: 22.0, - ), - ), - ), - ], - ), - Container( - height: 52.0, - child: Center( - child: Text( - text, - style: TextStyle(fontSize: 16.0, color: textColor), + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: onPressed, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(radius), ), + )), + child: Stack( + children: [ + Row( + mainAxisAlignment: mainAxisAlignment, + children: [ + Container( + width: 26.0, + height: 52.0, + decoration: BoxDecoration(shape: BoxShape.circle, color: iconBackgroundColor), + child: Center(child: Icon(iconData, color: iconColor, size: 22.0)), + ), + ], ), - ) - ], - ), - ), - ), + Container( + height: 52.0, + child: Center( + child: Text(text, style: TextStyle(fontSize: 16.0, color: textColor)), + ), + ) + ], + ), + )), ); } } class PrimaryImageButton extends StatelessWidget { - const PrimaryImageButton({ - required this.onPressed, - required this.image, - required this.text, - required this.color, - required this.textColor, - this.borderColor = Colors.transparent, - super.key, - }); + const PrimaryImageButton( + {required this.onPressed, + required this.image, + required this.text, + required this.color, + required this.textColor, + this.borderColor = Colors.transparent, super.key}); final VoidCallback onPressed; final Image image; @@ -223,41 +197,32 @@ class PrimaryImageButton extends StatelessWidget { @override Widget build(BuildContext context) { return ConstrainedBox( - constraints: BoxConstraints( - maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint, - ), + constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: SizedBox( - width: double.infinity, - height: 52.0, - child: TextButton( - onPressed: onPressed, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(color), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(26.0), - ), - ), - ), - child: Center( - child: Row( - mainAxisSize: MainAxisSize.min, - children: [ - image, - SizedBox(width: 15), - Text( - text, - style: TextStyle( - fontSize: 15, - fontWeight: FontWeight.w600, - color: textColor, - ), - ) - ], - ), - ), - ), - ), + width: double.infinity, + height: 52.0, + child: TextButton( + onPressed: onPressed, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(color), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(26.0), + ), + )), + child: Center( + child: Row( + mainAxisSize: MainAxisSize.min, + children: [ + image, + SizedBox(width: 15), + Text( + text, + style: TextStyle(fontSize: 15, fontWeight: FontWeight.w600, color: textColor), + ) + ], + ), + ))), ); } } diff --git a/lib/src/widgets/scollable_with_bottom_section.dart b/lib/src/widgets/scollable_with_bottom_section.dart index e15be610e2..07a0702049 100644 --- a/lib/src/widgets/scollable_with_bottom_section.dart +++ b/lib/src/widgets/scollable_with_bottom_section.dart @@ -9,6 +9,7 @@ class ScrollableWithBottomSection extends StatefulWidget { this.contentPadding, this.bottomSectionPadding, this.topSectionPadding, + this.scrollableKey, }); final Widget content; @@ -17,6 +18,7 @@ class ScrollableWithBottomSection extends StatefulWidget { final EdgeInsets? contentPadding; final EdgeInsets? bottomSectionPadding; final EdgeInsets? topSectionPadding; + final Key? scrollableKey; @override ScrollableWithBottomSectionState createState() => ScrollableWithBottomSectionState(); @@ -35,6 +37,7 @@ class ScrollableWithBottomSectionState extends State { Padding( padding: EdgeInsets.only(right: 40, top: 10), child: ValidatableAnnotatedEditableText( + key: ValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'), cursorColor: Colors.blue, backgroundCursorColor: Colors.blue, validStyle: TextStyle( @@ -110,6 +112,7 @@ class SeedWidgetState extends State { width: 32, height: 32, child: InkWell( + key: ValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'), onTap: () async => _pasteText(), child: Container( padding: EdgeInsets.all(8), From 8ec38e10ffab2a9863ccc2294e9de48ecbbf5e49 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 25 Jun 2024 18:12:38 +0100 Subject: [PATCH 03/69] test: Dashboard view test and linking to flow --- integration_test/app_test.dart | 15 +++- .../components/common_checks.dart | 12 ++- .../integration_response_data.json | 1 + .../robots/dashboard_page_robot.dart | 75 +++++++++++++++++++ .../robots/disclaimer_page_robot.dart | 2 +- .../robots/exchange_page_robot.dart | 0 .../restore_from_seed_or_key_robot.dart | 11 ++- .../robots/restore_options_page_robot.dart | 6 +- .../robots/setup_pin_code_robot.dart | 8 +- .../robots/welcome_page_robot.dart | 4 +- lib/src/screens/dashboard/dashboard_page.dart | 6 ++ .../dashboard/widgets/action_button.dart | 16 ++-- .../dashboard/widgets/sync_indicator.dart | 6 +- test_driver/integration_test.dart | 10 +-- 14 files changed, 137 insertions(+), 35 deletions(-) create mode 100644 integration_test/integration_response_data.json create mode 100644 integration_test/robots/dashboard_page_robot.dart create mode 100644 integration_test/robots/exchange_page_robot.dart diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index 252fc2ed58..ab70fa5e85 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -1,12 +1,11 @@ -import 'dart:io'; import 'package:cake_wallet/main.dart' as app; -import 'package:cake_wallet/themes/theme_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'robots/dashboard_page_robot.dart'; import 'robots/disclaimer_page_robot.dart'; import 'robots/new_wallet_type_page_robot.dart'; import 'robots/restore_from_seed_or_key_robot.dart'; @@ -32,6 +31,7 @@ void main() { RestoreOptionsPageRobot restoreOptionsPageRobot; NewWalletTypePageRobot newWalletTypePageRobot; RestoreFromSeedOrKeysPageRobot restoreFromSeedOrKeysPageRobot; + DashboardPageRobot dashboardPageRobot; group('Startup Test', () { testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL', @@ -42,6 +42,7 @@ void main() { restoreOptionsPageRobot = RestoreOptionsPageRobot(tester); newWalletTypePageRobot = NewWalletTypePageRobot(tester); restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester); + dashboardPageRobot = DashboardPageRobot(tester); await app.main(); await tester.pumpAndSettle(); @@ -136,6 +137,16 @@ void main() { 'noble define inflict tackle sweet essence mention bicycle word hard patient ketchup', ); await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonTapped(); + + // ----------- RestoreFromSeedOrKeys Page ------------- + await dashboardPageRobot.isDashboardPage(); + dashboardPageRobot.confirmServiceUpdateButtonDisplays(); + dashboardPageRobot.confirmMenuButtonDisplays(); + dashboardPageRobot.confirmSyncIndicatorButtonDisplays(); + await dashboardPageRobot.confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType.solana); + + await dashboardPageRobot.navigateToExchangePage(); + await Future.delayed(Duration(seconds: 5)); }); }); } diff --git a/integration_test/components/common_checks.dart b/integration_test/components/common_checks.dart index 90cdb710de..a09ea1e691 100644 --- a/integration_test/components/common_checks.dart +++ b/integration_test/components/common_checks.dart @@ -34,6 +34,16 @@ class CommonTestCases { expect(typeWidget, findsOneWidget); } + Future swipePage({bool swipeRight = true}) async { + await tester.drag(find.byType(PageView), Offset(swipeRight ? -300 : 300, 0)); + await tester.pumpAndSettle(); + } + + Future swipeByPageKey({required String key, bool swipeRight = true}) async { + await tester.drag(find.byKey(ValueKey(key)), Offset(swipeRight ? -300 : 300, 0)); + await tester.pumpAndSettle(); + } + Future goBack() async { tester.printToConsole('Routing back to previous screen'); final NavigatorState navigator = tester.state(find.byType(Navigator)); @@ -41,5 +51,5 @@ class CommonTestCases { await tester.pumpAndSettle(); } - void defaultSleepTime() => sleep(Duration(seconds: 2)); + Future defaultSleepTime({int seconds = 2})async => await Future.delayed(Duration(seconds: seconds)); } diff --git a/integration_test/integration_response_data.json b/integration_test/integration_response_data.json new file mode 100644 index 0000000000..ec747fa47d --- /dev/null +++ b/integration_test/integration_response_data.json @@ -0,0 +1 @@ +null \ No newline at end of file diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart new file mode 100644 index 0000000000..4d3241827d --- /dev/null +++ b/integration_test/robots/dashboard_page_robot.dart @@ -0,0 +1,75 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; + +class DashboardPageRobot { + DashboardPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isDashboardPage() async { + await commonTestCases.isSpecificPage(); + } + + void confirmServiceUpdateButtonDisplays() { + commonTestCases.hasKey('dashboard_page_services_update_button_key'); + } + + void confirmSyncIndicatorButtonDisplays() { + commonTestCases.hasKey('dashboard_page_sync_indicator_button_key'); + } + + void confirmMenuButtonDisplays() { + commonTestCases.hasKey('dashboard_page_wallet_menu_button_key'); + } + + Future confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type, + {bool isHaven = false}) async { + //Balance Page + final walletName = walletTypeToString(type); + final assetName = isHaven ? '$walletName Assets' : walletName; + commonTestCases.hasText(assetName); + + // Swipe to Cake features Page + await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false); + await commonTestCases.defaultSleepTime(); + commonTestCases.hasText('Cake ${S.current.features}'); + + // Swipe back to balance + await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key'); + await commonTestCases.defaultSleepTime(); + + // Swipe to Transactions Page + await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key'); + await commonTestCases.defaultSleepTime(); + commonTestCases.hasText(S.current.transactions); + + // Swipe back to balance + await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false); + await commonTestCases.defaultSleepTime(seconds: 5); + } + + Future navigateToBuyPage() async { + await commonTestCases.tapItemByKey('dashboard_page_${S.current.buy}_action_button_key'); + } + + Future navigateToSendPage() async { + await commonTestCases.tapItemByKey('dashboard_page_${S.current.buy}_action_button_key'); + } + + Future navigateToSellPage() async { + await commonTestCases.tapItemByKey('dashboard_page_${S.current.sell}_action_button_key'); + } + + Future navigateToReceivePage() async { + await commonTestCases.tapItemByKey('dashboard_page_${S.current.receive}_action_button_key'); + } + + Future navigateToExchangePage() async { + await commonTestCases.tapItemByKey('dashboard_page_${S.current.exchange}_action_button_key'); + } +} diff --git a/integration_test/robots/disclaimer_page_robot.dart b/integration_test/robots/disclaimer_page_robot.dart index a102ec8a1b..642f56bca1 100644 --- a/integration_test/robots/disclaimer_page_robot.dart +++ b/integration_test/robots/disclaimer_page_robot.dart @@ -29,7 +29,7 @@ class DisclaimerPageRobot { final checkBox = find.byKey(ValueKey('disclaimer_check_key')); await tester.tap(checkBox); await tester.pumpAndSettle(); - commonTestCases.defaultSleepTime(); + await commonTestCases.defaultSleepTime(); } Future tapAcceptButton() async { diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart new file mode 100644 index 0000000000..e69de29bb2 diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index e57551f61c..6ceebb1d5f 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -1,5 +1,4 @@ import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/restore/wallet_restore_from_seed_form.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; import 'package:flutter/material.dart'; @@ -30,9 +29,8 @@ class RestoreFromSeedOrKeysPageRobot { commonTestCases.hasText(S.current.private_key, hasWidget: false); commonTestCases.hasText(S.current.restore_title_from_keys, hasWidget: false); - await tester.drag(find.byType(PageView), Offset(-300, 0)); - await tester.pumpAndSettle(); - commonTestCases.defaultSleepTime(); + await commonTestCases.swipePage(); + await commonTestCases.defaultSleepTime(); commonTestCases.hasText(S.current.wallet_name); commonTestCases.hasText(S.current.private_key); @@ -41,8 +39,7 @@ class RestoreFromSeedOrKeysPageRobot { commonTestCases.hasText(S.current.enter_seed_phrase, hasWidget: false); commonTestCases.hasText(S.current.restore_title_from_seed, hasWidget: false); - await tester.drag(find.byType(PageView), Offset(300, 0)); - await tester.pumpAndSettle(); + await commonTestCases.swipePage(swipeRight: false); } void confirmRestoreButtonDisplays() { @@ -70,6 +67,7 @@ class RestoreFromSeedOrKeysPageRobot { seedTextState.widget.controller.text = text; await tester.pumpAndSettle(); + await Future.delayed(Duration(seconds: 3)); } Future onPasteSeedPhraseButtonTapped() async { @@ -78,5 +76,6 @@ class RestoreFromSeedOrKeysPageRobot { Future onRestoreWalletButtonTapped() async { await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key'); + await commonTestCases.defaultSleepTime(seconds: 15); } } diff --git a/integration_test/robots/restore_options_page_robot.dart b/integration_test/robots/restore_options_page_robot.dart index faa6a24369..7f9c99be3c 100644 --- a/integration_test/robots/restore_options_page_robot.dart +++ b/integration_test/robots/restore_options_page_robot.dart @@ -23,19 +23,19 @@ class RestoreOptionsPageRobot { Future navigateToRestoreFromSeedsPage() async { tester.printToConsole('Routing to restore from seeds page'); await commonTestCases.tapItemByKey('restore_options_from_seeds_button_key'); - commonTestCases.defaultSleepTime(); + await commonTestCases.defaultSleepTime(); } Future navigateToRestoreFromBackupPage() async { tester.printToConsole('Routing to restore from backup page'); await commonTestCases.tapItemByKey('restore_options_from_backup_button_key'); - commonTestCases.defaultSleepTime(); + await commonTestCases.defaultSleepTime(); } Future navigateToRestoreFromHardwareWalletPage() async { tester.printToConsole('Routing to restore from hardware wallet page'); await commonTestCases.tapItemByKey('restore_options_from_hardware_wallet_button_key'); - commonTestCases.defaultSleepTime(); + await commonTestCases.defaultSleepTime(); } Future backAndVerify() async { diff --git a/integration_test/robots/setup_pin_code_robot.dart b/integration_test/robots/setup_pin_code_robot.dart index b64e63f1f2..3649bb5b3c 100644 --- a/integration_test/robots/setup_pin_code_robot.dart +++ b/integration_test/robots/setup_pin_code_robot.dart @@ -47,26 +47,22 @@ class SetupPinCodeRobot { await pushPinButton(0); expect(pinCodeState.pin, '0'); - tester.printToConsole(pinCodeState.pin); await pushPinButton(8); expect(pinCodeState.pin, '08'); - tester.printToConsole(pinCodeState.pin); await pushPinButton(0); expect(pinCodeState.pin, '080'); - tester.printToConsole(pinCodeState.pin); await pushPinButton(1); // the state is cleared once it get's to the last entry expect(pinCodeState.pin, isFirstEntry ? '' : '0801'); - tester.printToConsole(pinCodeState.pin); - commonTestCases.defaultSleepTime(); + await commonTestCases.defaultSleepTime(); } Future tapSuccessButton() async { await commonTestCases.tapItemByKey('setup_pin_code_success_button_key'); - commonTestCases.defaultSleepTime(); + await commonTestCases.defaultSleepTime(); } } diff --git a/integration_test/robots/welcome_page_robot.dart b/integration_test/robots/welcome_page_robot.dart index e61eaa8f4f..12a752f0c0 100644 --- a/integration_test/robots/welcome_page_robot.dart +++ b/integration_test/robots/welcome_page_robot.dart @@ -28,13 +28,13 @@ class WelcomePageRobot { Future navigateToCreateNewWalletPage() async { tester.printToConsole('Routing to create new wallet page'); await commonTestCases.tapItemByKey('welcome_page_create_new_wallet_button_key'); - commonTestCases.defaultSleepTime(); + await commonTestCases.defaultSleepTime(); } Future navigateToRestoreWalletPage() async { tester.printToConsole('Routing to restore wallet page'); await commonTestCases.tapItemByKey('welcome_page_restore_wallet_button_key'); - commonTestCases.defaultSleepTime(); + await commonTestCases.defaultSleepTime(); } Future backAndVerify() async { diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index bec10435e6..da85660c1a 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -146,6 +146,7 @@ class _DashboardPageView extends BasePage { return Observer( builder: (context) { return ServicesUpdatesWidget( + key: ValueKey('dashboard_page_services_update_button_key'), dashboardViewModel.getServicesStatus(), enabled: dashboardViewModel.isEnabledBulletinAction, ); @@ -156,6 +157,7 @@ class _DashboardPageView extends BasePage { @override Widget middle(BuildContext context) { return SyncIndicator( + key: ValueKey('dashboard_page_sync_indicator_button_key'), dashboardViewModel: dashboardViewModel, onTap: () => Navigator.of(context, rootNavigator: true).pushNamed(Routes.connectionSync), ); @@ -172,6 +174,7 @@ class _DashboardPageView extends BasePage { alignment: Alignment.centerRight, width: 40, child: TextButton( + key: ValueKey('dashboard_page_wallet_menu_button_key'), // FIX-ME: Style //highlightColor: Colors.transparent, //splashColor: Colors.transparent, @@ -225,6 +228,7 @@ class _DashboardPageView extends BasePage { child: Observer( builder: (context) { return PageView.builder( + key: ValueKey('dashboard_page_view_key'), controller: controller, itemCount: pages.length, itemBuilder: (context, index) => pages[index], @@ -286,6 +290,8 @@ class _DashboardPageView extends BasePage { button: true, enabled: (action.isEnabled?.call(dashboardViewModel) ?? true), child: ActionButton( + key: ValueKey( + 'dashboard_page_${action.name(context)}_action_button_key'), image: Image.asset( action.image, height: 24, diff --git a/lib/src/screens/dashboard/widgets/action_button.dart b/lib/src/screens/dashboard/widgets/action_button.dart index 23f5c2f934..49ebab3cdd 100644 --- a/lib/src/screens/dashboard/widgets/action_button.dart +++ b/lib/src/screens/dashboard/widgets/action_button.dart @@ -2,13 +2,15 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; class ActionButton extends StatelessWidget { - ActionButton( - {required this.image, - required this.title, - this.route, - this.onClick, - this.alignment = Alignment.center, - this.textColor}); + ActionButton({ + required this.image, + required this.title, + this.route, + this.onClick, + this.alignment = Alignment.center, + this.textColor, + super.key, + }); final Image image; final String title; diff --git a/lib/src/screens/dashboard/widgets/sync_indicator.dart b/lib/src/screens/dashboard/widgets/sync_indicator.dart index 52e596a834..27b3d01092 100644 --- a/lib/src/screens/dashboard/widgets/sync_indicator.dart +++ b/lib/src/screens/dashboard/widgets/sync_indicator.dart @@ -7,7 +7,11 @@ import 'package:cw_core/sync_status.dart'; import 'package:cake_wallet/src/screens/dashboard/widgets/sync_indicator_icon.dart'; class SyncIndicator extends StatelessWidget { - SyncIndicator({required this.dashboardViewModel, required this.onTap}); + SyncIndicator({ + required this.dashboardViewModel, + required this.onTap, + super.key, + }); final DashboardViewModel dashboardViewModel; final Function() onTap; diff --git a/test_driver/integration_test.dart b/test_driver/integration_test.dart index 533cfb6e7b..c759dc604d 100644 --- a/test_driver/integration_test.dart +++ b/test_driver/integration_test.dart @@ -17,18 +17,16 @@ Future main() async { ), ); - if (data != null) { - final resultString = _encodeJson(data); - await file.writeAsString(resultString); - } + final resultString = _encodeJson(data); + await file.writeAsString(resultString); }, ); } -String _encodeJson(Map jsonObject) { +String _encodeJson(Map? jsonObject) { return _prettyEncoder.convert(jsonObject); } const _prettyEncoder = JsonEncoder.withIndent(' '); const _testOutputFilename = 'integration_response_data'; -const _destinationDirectory = 'integration_test'; \ No newline at end of file +const _destinationDirectory = 'integration_test'; From 66af74f0c0b3bebe1a1231b21ab7ad7f3237a89b Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 27 Jun 2024 07:17:06 +0100 Subject: [PATCH 04/69] feat: Testing the Exchange flow section, selecting sending and receiving currencies --- integration_test/app_test.dart | 31 +++- .../components/common_checks.dart | 18 ++- .../robots/dashboard_page_robot.dart | 6 +- .../robots/exchange_page_robot.dart | 152 ++++++++++++++++++ .../restore_from_seed_or_key_robot.dart | 14 +- .../robots/restore_options_page_robot.dart | 8 +- .../robots/setup_pin_code_robot.dart | 4 +- lib/src/screens/exchange/exchange_page.dart | 29 ++-- .../exchange/exchange_template_page.dart | 2 + .../exchange/widgets/currency_picker.dart | 3 +- .../exchange/widgets/exchange_card.dart | 65 +++++--- lib/src/widgets/address_text_field.dart | 2 +- lib/src/widgets/alert_close_button.dart | 7 +- lib/src/widgets/picker.dart | 20 ++- lib/src/widgets/picker_wrapper_widget.dart | 17 +- lib/src/widgets/search_bar_widget.dart | 1 + 16 files changed, 315 insertions(+), 64 deletions(-) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index ab70fa5e85..1b0277d296 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -1,5 +1,5 @@ - import 'package:cake_wallet/main.dart' as app; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -7,6 +7,7 @@ import 'package:integration_test/integration_test.dart'; import 'robots/dashboard_page_robot.dart'; import 'robots/disclaimer_page_robot.dart'; +import 'robots/exchange_page_robot.dart'; import 'robots/new_wallet_type_page_robot.dart'; import 'robots/restore_from_seed_or_key_robot.dart'; import 'robots/restore_options_page_robot.dart'; @@ -32,6 +33,7 @@ void main() { NewWalletTypePageRobot newWalletTypePageRobot; RestoreFromSeedOrKeysPageRobot restoreFromSeedOrKeysPageRobot; DashboardPageRobot dashboardPageRobot; + ExchangePageRobot exchangePageRobot; group('Startup Test', () { testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL', @@ -43,6 +45,7 @@ void main() { newWalletTypePageRobot = NewWalletTypePageRobot(tester); restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester); dashboardPageRobot = DashboardPageRobot(tester); + exchangePageRobot = ExchangePageRobot(tester); await app.main(); await tester.pumpAndSettle(); @@ -139,14 +142,28 @@ void main() { await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonTapped(); // ----------- RestoreFromSeedOrKeys Page ------------- - await dashboardPageRobot.isDashboardPage(); - dashboardPageRobot.confirmServiceUpdateButtonDisplays(); - dashboardPageRobot.confirmMenuButtonDisplays(); - dashboardPageRobot.confirmSyncIndicatorButtonDisplays(); - await dashboardPageRobot.confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType.solana); + // await dashboardPageRobot.isDashboardPage(); + // dashboardPageRobot.confirmServiceUpdateButtonDisplays(); + // dashboardPageRobot.confirmMenuButtonDisplays(); + // dashboardPageRobot.confirmSyncIndicatorButtonDisplays(); + // await dashboardPageRobot.confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType.solana); await dashboardPageRobot.navigateToExchangePage(); - await Future.delayed(Duration(seconds: 5)); + await Future.delayed(Duration(seconds: 2)); + + // ----------- Exchange Page ------------- + await exchangePageRobot.isExchangePage(); + exchangePageRobot.hasTitle(); + exchangePageRobot.hasResetButton(); + await exchangePageRobot.displayBothExchangeCards(); + exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards(); + exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards(); + + await exchangePageRobot.selectDepositCurrency(CryptoCurrency.btc); + + await exchangePageRobot.selectReceiveCurrency(CryptoCurrency.usdtSol); + + await Future.delayed(Duration(seconds: 10)); }); }); } diff --git a/integration_test/components/common_checks.dart b/integration_test/components/common_checks.dart index a09ea1e691..2816b75b94 100644 --- a/integration_test/components/common_checks.dart +++ b/integration_test/components/common_checks.dart @@ -29,7 +29,7 @@ class CommonTestCases { expect(typeWidget, findsOneWidget); } - void hasKey(String key) { + void hasValueKey(String key) { final typeWidget = find.byKey(ValueKey(key)); expect(typeWidget, findsOneWidget); } @@ -51,5 +51,19 @@ class CommonTestCases { await tester.pumpAndSettle(); } - Future defaultSleepTime({int seconds = 2})async => await Future.delayed(Duration(seconds: seconds)); + Future scrollUntilVisible(String childKey, String parentScrollableKey, {double delta = 300}) async { + final scrollableWidget = find.descendant( + of: find.byKey(Key(parentScrollableKey)), + matching: find.byType(Scrollable), + ); + + await tester.scrollUntilVisible( + find.byKey(ValueKey(childKey)), + delta, + scrollable: scrollableWidget, + ); + } + + Future defaultSleepTime({int seconds = 2}) async => + await Future.delayed(Duration(seconds: seconds)); } diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart index 4d3241827d..d8fb4211ae 100644 --- a/integration_test/robots/dashboard_page_robot.dart +++ b/integration_test/robots/dashboard_page_robot.dart @@ -16,15 +16,15 @@ class DashboardPageRobot { } void confirmServiceUpdateButtonDisplays() { - commonTestCases.hasKey('dashboard_page_services_update_button_key'); + commonTestCases.hasValueKey('dashboard_page_services_update_button_key'); } void confirmSyncIndicatorButtonDisplays() { - commonTestCases.hasKey('dashboard_page_sync_indicator_button_key'); + commonTestCases.hasValueKey('dashboard_page_sync_indicator_button_key'); } void confirmMenuButtonDisplays() { - commonTestCases.hasKey('dashboard_page_wallet_menu_button_key'); + commonTestCases.hasValueKey('dashboard_page_wallet_menu_button_key'); } Future confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type, diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart index e69de29bb2..bf034efaa9 100644 --- a/integration_test/robots/exchange_page_robot.dart +++ b/integration_test/robots/exchange_page_robot.dart @@ -0,0 +1,152 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; +import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; + +class ExchangePageRobot { + ExchangePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isExchangePage() async { + await commonTestCases.isSpecificPage(); + } + + void hasTitle() { + // commonTestCases.hasText(S.current.exchange); + } + + void hasResetButton() { + commonTestCases.hasText(S.current.reset); + } + + void displaysPresentProviderPicker() { + commonTestCases.hasType(); + } + + Future displayBothExchangeCards() async { + final ExchangePage exchangeCard = tester.widget( + find.byType(ExchangePage), + ); + + final depositKey = exchangeCard.depositKey; + final receiveKey = exchangeCard.receiveKey; + + final depositExchangeCard = find.byKey(depositKey); + expect(depositExchangeCard, findsOneWidget); + + final receiveExchangeCard = find.byKey(receiveKey); + expect(receiveExchangeCard, findsOneWidget); + } + + void confirmRightComponentsDisplayOnDepositExchangeCards() { + ExchangePage exchangePage = tester.widget(find.byType(ExchangePage)); + final exchangeViewModel = exchangePage.exchangeViewModel; + final depositCardPrefix = 'deposit_exchange_card'; + + commonTestCases.hasValueKey('${depositCardPrefix}_title_key'); + commonTestCases.hasValueKey('${depositCardPrefix}_currency_picker_button_key'); + commonTestCases.hasValueKey('${depositCardPrefix}_selected_currency_text_key'); + commonTestCases.hasValueKey('${depositCardPrefix}_amount_textfield_key'); + commonTestCases.hasValueKey('${depositCardPrefix}_min_limit_text_key'); + + final initialCurrency = exchangeViewModel.depositCurrency; + if (initialCurrency.tag != null) { + commonTestCases.hasValueKey('${depositCardPrefix}_selected_currency_tag_text_key'); + } + + if (exchangeViewModel.hasAllAmount) { + commonTestCases.hasValueKey('${depositCardPrefix}_send_all_button_key'); + } + + if (exchangeViewModel.isMoneroWallet) { + commonTestCases.hasValueKey('${depositCardPrefix}_address_book_button_key'); + } + + if (exchangeViewModel.isDepositAddressEnabled) { + commonTestCases.hasValueKey('${depositCardPrefix}_editable_address_textfield_key'); + } else { + commonTestCases.hasValueKey('${depositCardPrefix}_non_editable_address_textfield_key'); + commonTestCases.hasValueKey('${depositCardPrefix}_copy_refund_address_button_key'); + } + + // commonTestCases.hasValueKey('${depositCardPrefix}_max_limit_text_key'); + } + + void confirmRightComponentsDisplayOnReceiveExchangeCards() { + ExchangePage exchangePage = tester.widget(find.byType(ExchangePage)); + final exchangeViewModel = exchangePage.exchangeViewModel; + final receiveCardPrefix = 'receive_exchange_card'; + + commonTestCases.hasValueKey('${receiveCardPrefix}_title_key'); + commonTestCases.hasValueKey('${receiveCardPrefix}_currency_picker_button_key'); + commonTestCases.hasValueKey('${receiveCardPrefix}_selected_currency_text_key'); + commonTestCases.hasValueKey('${receiveCardPrefix}_amount_textfield_key'); + commonTestCases.hasValueKey('${receiveCardPrefix}_min_limit_text_key'); + + final initialCurrency = exchangeViewModel.receiveCurrency; + if (initialCurrency.tag != null) { + commonTestCases.hasValueKey('${receiveCardPrefix}_selected_currency_tag_text_key'); + } + + if (exchangeViewModel.hasAllAmount) { + commonTestCases.hasValueKey('${receiveCardPrefix}_send_all_button_key'); + } + + if (exchangeViewModel.isMoneroWallet) { + commonTestCases.hasValueKey('${receiveCardPrefix}_address_book_button_key'); + } + + commonTestCases.hasValueKey('${receiveCardPrefix}_editable_address_textfield_key'); + } + + Future selectDepositCurrency(CryptoCurrency depositCurrency) async { + final depositPrefix = 'deposit_exchange_card'; + final currencyPickerKey = '${depositPrefix}_currency_picker_button_key'; + final currencyPickerDialogKey = '${depositPrefix}_currency_picker_dialog_button_key'; + + await commonTestCases.tapItemByKey(currencyPickerKey); + commonTestCases.hasValueKey(currencyPickerDialogKey); + + await commonTestCases.scrollUntilVisible( + 'picker_items_index_${depositCurrency.name}_button_key', + 'picker_scrollbar_key', + ); + await commonTestCases.defaultSleepTime(); + + await commonTestCases.tapItemByKey('picker_items_index_${depositCurrency.name}_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future selectReceiveCurrency(CryptoCurrency receiveCurrency) async { + final receivePrefix = 'receive_exchange_card'; + final currencyPickerKey = '${receivePrefix}_currency_picker_button_key'; + final currencyPickerDialogKey = '${receivePrefix}_currency_picker_dialog_button_key'; + + await commonTestCases.tapItemByKey(currencyPickerKey); + + commonTestCases.hasValueKey(currencyPickerDialogKey); + + await commonTestCases.scrollUntilVisible( + 'picker_items_index_${receiveCurrency.name}_button_key', + 'picker_scrollbar_key', + ); + await commonTestCases.defaultSleepTime(); + + await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future enterDepositAddress(String depositAddress) async { + final amountTextField = find.byKey(ValueKey('deposit_exchange_card_amount_textfield_key')); + } + + Future enterReceiveAddress(String receiveAddress) async {} +} diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index 6ceebb1d5f..96cde0d892 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -21,10 +21,10 @@ class RestoreFromSeedOrKeysPageRobot { commonTestCases.hasText(S.current.enter_seed_phrase); commonTestCases.hasText(S.current.restore_title_from_seed); - commonTestCases.hasKey('wallet_restore_from_seed_wallet_name_textfield_key'); - commonTestCases.hasKey('wallet_restore_from_seed_wallet_name_refresh_button_key'); - commonTestCases.hasKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'); - commonTestCases.hasKey('wallet_restore_from_seed_wallet_seeds_textfield_key'); + commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_name_textfield_key'); + commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'); + commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'); + commonTestCases.hasValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'); commonTestCases.hasText(S.current.private_key, hasWidget: false); commonTestCases.hasText(S.current.restore_title_from_keys, hasWidget: false); @@ -43,11 +43,11 @@ class RestoreFromSeedOrKeysPageRobot { } void confirmRestoreButtonDisplays() { - commonTestCases.hasKey('wallet_restore_seed_or_key_restore_button_key'); + commonTestCases.hasValueKey('wallet_restore_seed_or_key_restore_button_key'); } void confirmAdvancedSettingButtonDisplays() { - commonTestCases.hasKey('wallet_restore_advanced_settings_button_key'); + commonTestCases.hasValueKey('wallet_restore_advanced_settings_button_key'); } Future enterWalletNameText(String walletName) async { @@ -76,6 +76,6 @@ class RestoreFromSeedOrKeysPageRobot { Future onRestoreWalletButtonTapped() async { await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key'); - await commonTestCases.defaultSleepTime(seconds: 15); + // await commonTestCases.defaultSleepTime(seconds: 15); } } diff --git a/integration_test/robots/restore_options_page_robot.dart b/integration_test/robots/restore_options_page_robot.dart index 7f9c99be3c..2037b913a6 100644 --- a/integration_test/robots/restore_options_page_robot.dart +++ b/integration_test/robots/restore_options_page_robot.dart @@ -14,10 +14,10 @@ class RestoreOptionsPageRobot { } void hasRestoreOptionsButton() { - commonTestCases.hasKey('restore_options_from_seeds_button_key'); - commonTestCases.hasKey('restore_options_from_backup_button_key'); - commonTestCases.hasKey('restore_options_from_hardware_wallet_button_key'); - commonTestCases.hasKey('restore_options_from_qr_button_key'); + commonTestCases.hasValueKey('restore_options_from_seeds_button_key'); + commonTestCases.hasValueKey('restore_options_from_backup_button_key'); + commonTestCases.hasValueKey('restore_options_from_hardware_wallet_button_key'); + commonTestCases.hasValueKey('restore_options_from_qr_button_key'); } Future navigateToRestoreFromSeedsPage() async { diff --git a/integration_test/robots/setup_pin_code_robot.dart b/integration_test/robots/setup_pin_code_robot.dart index 3649bb5b3c..2caaddc8ac 100644 --- a/integration_test/robots/setup_pin_code_robot.dart +++ b/integration_test/robots/setup_pin_code_robot.dart @@ -28,11 +28,11 @@ class SetupPinCodeRobot { void hasNumberButtonsVisible() { // Confirmation for buttons 1-9 for (var i = 1; i < 10; i++) { - commonTestCases.hasKey('pin_code_button_${i}_key'); + commonTestCases.hasValueKey('pin_code_button_${i}_key'); } // Confirmation for 0 button - commonTestCases.hasKey('pin_code_button_0_key'); + commonTestCases.hasValueKey('pin_code_button_0_key'); } Future pushPinButton(int index) async { diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index 5c064df27e..fc4d6cbf7a 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -101,11 +101,11 @@ class ExchangePage extends BasePage { @override Function(BuildContext)? get pushToNextWidget => (context) { - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.focusedChild?.unfocus(); - } - }; + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.focusedChild?.unfocus(); + } + }; @override Widget middle(BuildContext context) => Row( @@ -239,6 +239,7 @@ class ExchangePage extends BasePage { ), Observer( builder: (_) => LoadingPrimaryButton( + key: ValueKey('exchange_page_exchange_button_key'), text: S.of(context).exchange, onPressed: () { if (_formKey.currentState != null && @@ -340,7 +341,6 @@ class ExchangePage extends BasePage { void applyTemplate( BuildContext context, ExchangeViewModel exchangeViewModel, ExchangeTemplate template) async { - final depositCryptoCurrency = CryptoCurrency.fromString(template.depositCurrency); final receiveCryptoCurrency = CryptoCurrency.fromString(template.receiveCurrency); @@ -354,10 +354,12 @@ class ExchangePage extends BasePage { exchangeViewModel.isFixedRateMode = false; var domain = template.depositAddress; - exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, depositCryptoCurrency); + exchangeViewModel.depositAddress = + await fetchParsedAddress(context, domain, depositCryptoCurrency); domain = template.receiveAddress; - exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, receiveCryptoCurrency); + exchangeViewModel.receiveAddress = + await fetchParsedAddress(context, domain, receiveCryptoCurrency); } void _setReactions(BuildContext context, ExchangeViewModel exchangeViewModel) { @@ -529,14 +531,16 @@ class ExchangePage extends BasePage { _depositAddressFocus.addListener(() async { if (!_depositAddressFocus.hasFocus && depositAddressController.text.isNotEmpty) { final domain = depositAddressController.text; - exchangeViewModel.depositAddress = await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); + exchangeViewModel.depositAddress = + await fetchParsedAddress(context, domain, exchangeViewModel.depositCurrency); } }); _receiveAddressFocus.addListener(() async { if (!_receiveAddressFocus.hasFocus && receiveAddressController.text.isNotEmpty) { final domain = receiveAddressController.text; - exchangeViewModel.receiveAddress = await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); + exchangeViewModel.receiveAddress = + await fetchParsedAddress(context, domain, exchangeViewModel.receiveCurrency); } }); @@ -589,7 +593,8 @@ class ExchangePage extends BasePage { } } - Future fetchParsedAddress(BuildContext context, String domain, CryptoCurrency currency) async { + Future fetchParsedAddress( + BuildContext context, String domain, CryptoCurrency currency) async { final parsedAddress = await getIt.get().resolve(context, domain, currency); final address = await extractAddressFromParsed(context, parsedAddress); return address; @@ -619,6 +624,7 @@ class ExchangePage extends BasePage { Widget _exchangeCardsSection(BuildContext context) { final firstExchangeCard = Observer( builder: (_) => ExchangeCard( + cardInstanceName: 'deposit_exchange_card', onDispose: disposeBestRateSync, hasAllAmount: exchangeViewModel.hasAllAmount, allAmount: exchangeViewModel.hasAllAmount @@ -689,6 +695,7 @@ class ExchangePage extends BasePage { final secondExchangeCard = Observer( builder: (_) => ExchangeCard( + cardInstanceName: 'receive_exchange_card', onDispose: disposeBestRateSync, amountFocusNode: _receiveAmountFocus, addressFocusNode: _receiveAddressFocus, diff --git a/lib/src/screens/exchange/exchange_template_page.dart b/lib/src/screens/exchange/exchange_template_page.dart index d24c91dad1..4edc9095a7 100644 --- a/lib/src/screens/exchange/exchange_template_page.dart +++ b/lib/src/screens/exchange/exchange_template_page.dart @@ -121,6 +121,7 @@ class ExchangeTemplatePage extends BasePage { padding: EdgeInsets.fromLTRB(24, 100, 24, 32), child: Observer( builder: (_) => ExchangeCard( + cardInstanceName: 'deposit_exchange_template_card', amountFocusNode: _depositAmountFocus, key: depositKey, title: S.of(context).you_will_send, @@ -157,6 +158,7 @@ class ExchangeTemplatePage extends BasePage { padding: EdgeInsets.only(top: 29, left: 24, right: 24), child: Observer( builder: (_) => ExchangeCard( + cardInstanceName: 'receive_exchange_template_card', amountFocusNode: _receiveAmountFocus, key: receiveKey, title: S.of(context).you_will_get, diff --git a/lib/src/screens/exchange/widgets/currency_picker.dart b/lib/src/screens/exchange/widgets/currency_picker.dart index 0fe1d4e670..8c6f5e214c 100644 --- a/lib/src/screens/exchange/widgets/currency_picker.dart +++ b/lib/src/screens/exchange/widgets/currency_picker.dart @@ -12,7 +12,8 @@ class CurrencyPicker extends StatefulWidget { this.title, this.hintText, this.isMoneroWallet = false, - this.isConvertFrom = false}); + this.isConvertFrom = false, + super.key}); final int selectedAtIndex; final List items; diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 760b0c1372..1bc0c5942b 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -44,8 +44,9 @@ class ExchangeCard extends StatefulWidget { this.allAmount, this.onPushPasteButton, this.onPushAddressBookButton, - this.onDispose}) - : super(key: key); + this.onDispose, + required this.cardInstanceName, + }) : super(key: key); final List currencies; final Function(CryptoCurrency) onCurrencySelected; @@ -73,6 +74,7 @@ class ExchangeCard extends StatefulWidget { final void Function(BuildContext context)? onPushPasteButton; final void Function(BuildContext context)? onPushAddressBookButton; final Function()? onDispose; + final String cardInstanceName; @override ExchangeCardState createState() => ExchangeCardState(); @@ -88,11 +90,13 @@ class ExchangeCardState extends State { _walletName = '', _selectedCurrency = CryptoCurrency.btc, _isAmountEstimated = false, - _isMoneroWallet = false; + _isMoneroWallet = false, + _cardInstanceName = ''; final addressController = TextEditingController(); final amountController = TextEditingController(); + String _cardInstanceName; String _title; String? _min; String? _max; @@ -105,6 +109,7 @@ class ExchangeCardState extends State { @override void initState() { + _cardInstanceName = widget.cardInstanceName; _title = widget.title; _isAmountEditable = widget.initialIsAmountEditable; _isAddressEditable = widget.initialIsAddressEditable; @@ -183,6 +188,7 @@ class ExchangeCardState extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Text( + key: ValueKey('${_cardInstanceName}_title_key'), _title, style: TextStyle( fontSize: 18, @@ -200,6 +206,7 @@ class ExchangeCardState extends State { height: 32, color: widget.currencyButtonColor, child: InkWell( + key: ValueKey('${_cardInstanceName}_currency_picker_button_key'), onTap: () => _presentPicker(context), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -209,9 +216,12 @@ class ExchangeCardState extends State { padding: EdgeInsets.only(right: 5), child: widget.imageArrow, ), - Text(_selectedCurrency.toString(), - style: TextStyle( - fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white)) + Text( + key: ValueKey('${_cardInstanceName}_selected_currency_text_key'), + _selectedCurrency.toString(), + style: TextStyle( + fontWeight: FontWeight.w600, fontSize: 16, color: Colors.white), + ) ]), ), ), @@ -227,13 +237,16 @@ class ExchangeCardState extends State { child: Center( child: Padding( padding: const EdgeInsets.all(6.0), - child: Text(_selectedCurrency.tag!, - style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.bold, - color: Theme.of(context) - .extension()! - .textFieldButtonIconColor)), + child: Text( + key: ValueKey('${_cardInstanceName}_selected_currency_tag_text_key'), + _selectedCurrency.tag!, + style: TextStyle( + fontSize: 12, + fontWeight: FontWeight.bold, + color: Theme.of(context) + .extension()! + .textFieldButtonIconColor), + ), ), ), ), @@ -252,6 +265,7 @@ class ExchangeCardState extends State { child: FocusTraversalOrder( order: NumericFocusOrder(1), child: BaseTextFormField( + key: ValueKey('${_cardInstanceName}_amount_textfield_key'), focusNode: widget.amountFocusNode, controller: amountController, enabled: _isAmountEditable, @@ -272,9 +286,7 @@ class ExchangeCardState extends State { color: Theme.of(context) .extension()! .hintTextColor), - validator: _isAmountEditable - ? widget.currencyValueValidator - : null), + validator: _isAmountEditable ? widget.currencyValueValidator : null), ), ), if (widget.hasAllAmount) @@ -287,6 +299,7 @@ class ExchangeCardState extends State { .textFieldButtonColor, borderRadius: BorderRadius.all(Radius.circular(6))), child: InkWell( + key: ValueKey('${_cardInstanceName}_send_all_button_key'), onTap: () => widget.allAmount?.call(), child: Center( child: Text(S.of(context).all, @@ -313,6 +326,7 @@ class ExchangeCardState extends State { child: Row(mainAxisAlignment: MainAxisAlignment.start, children: [ _min != null ? Text( + key: ValueKey('${_cardInstanceName}_min_limit_text_key'), S.of(context).min_value(_min ?? '', _selectedCurrency.toString()), style: TextStyle( fontSize: 10, @@ -322,11 +336,15 @@ class ExchangeCardState extends State { : Offstage(), _min != null ? SizedBox(width: 10) : Offstage(), _max != null - ? Text(S.of(context).max_value(_max ?? '', _selectedCurrency.toString()), + ? Text( + key: ValueKey('${_cardInstanceName}_max_limit_text_key'), + S.of(context).max_value(_max ?? '', _selectedCurrency.toString()), style: TextStyle( - fontSize: 10, - height: 1.2, - color: Theme.of(context).extension()!.hintTextColor)) + fontSize: 10, + height: 1.2, + color: Theme.of(context).extension()!.hintTextColor, + ), + ) : Offstage(), ])), ), @@ -347,6 +365,7 @@ class ExchangeCardState extends State { child: Padding( padding: EdgeInsets.only(top: 20), child: AddressTextField( + key: ValueKey('${_cardInstanceName}_editable_address_textfield_key'), focusNode: widget.addressFocusNode, controller: addressController, onURIScanned: (uri) { @@ -387,6 +406,8 @@ class ExchangeCardState extends State { FocusTraversalOrder( order: NumericFocusOrder(3), child: BaseTextFormField( + key: ValueKey( + '${_cardInstanceName}_non_editable_address_textfield_key'), controller: addressController, borderColor: Colors.transparent, suffixIcon: SizedBox(width: _isMoneroWallet ? 80 : 36), @@ -410,6 +431,8 @@ class ExchangeCardState extends State { child: Semantics( label: S.of(context).address_book, child: InkWell( + key: ValueKey( + '${_cardInstanceName}_address_book_button_key'), onTap: () async { final contact = await Navigator.of(context).pushNamed( @@ -447,6 +470,7 @@ class ExchangeCardState extends State { child: Semantics( label: S.of(context).copy_address, child: InkWell( + key: ValueKey('${_cardInstanceName}_copy_refund_address_button_key'), onTap: () { Clipboard.setData( ClipboardData(text: addressController.text)); @@ -470,6 +494,7 @@ class ExchangeCardState extends State { showPopUp( context: context, builder: (_) => CurrencyPicker( + key: ValueKey('${_cardInstanceName}_currency_picker_dialog_button_key'), selectedAtIndex: widget.currencies.indexOf(_selectedCurrency), items: widget.currencies, hintText: S.of(context).search_currency, diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 0467b18a28..044e10f2f6 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -32,7 +32,7 @@ class AddressTextField extends StatelessWidget { this.onPushPasteButton, this.onPushAddressBookButton, this.onSelectedContact, - this.selectedCurrency}); + this.selectedCurrency, super.key}); static const prefixIconWidth = 34.0; static const prefixIconHeight = 34.0; diff --git a/lib/src/widgets/alert_close_button.dart b/lib/src/widgets/alert_close_button.dart index e3ff037a91..6ef0bdaa54 100644 --- a/lib/src/widgets/alert_close_button.dart +++ b/lib/src/widgets/alert_close_button.dart @@ -3,7 +3,12 @@ import 'package:cake_wallet/palette.dart'; import 'package:flutter/material.dart'; class AlertCloseButton extends StatelessWidget { - AlertCloseButton({this.image, this.bottom, this.onTap}); + AlertCloseButton({ + this.image, + this.bottom, + this.onTap, + super.key, + }); final VoidCallback? onTap; diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index b744d1db02..4c9601e0d6 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -11,6 +11,7 @@ import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; import 'package:cake_wallet/themes/extensions/cake_scrollbar_theme.dart'; import 'package:cake_wallet/themes/extensions/picker_theme.dart'; +//TODO(David): PickerWidget is intertwined and confusing as is, find a way to optimize? class Picker extends StatefulWidget { Picker({ required this.selectedAtIndex, @@ -153,6 +154,7 @@ class _PickerState extends State> { Container( padding: EdgeInsets.symmetric(horizontal: padding), child: Text( + key: ValueKey('picker_title_text_key'), widget.title!, textAlign: TextAlign.center, style: TextStyle( @@ -189,7 +191,10 @@ class _PickerState extends State> { Padding( padding: const EdgeInsets.all(16), child: SearchBarWidget( - searchController: searchController, hintText: widget.hintText), + key: ValueKey('picker_search_bar_key'), + searchController: searchController, + hintText: widget.hintText, + ), ), Divider( color: Theme.of(context).extension()!.dividerColor, @@ -203,6 +208,7 @@ class _PickerState extends State> { children: [ filteredItems.length > 3 ? Scrollbar( + key: ValueKey('picker_scrollbar_key'), controller: controller, child: itemsList(), ) @@ -213,6 +219,7 @@ class _PickerState extends State> { left: padding, right: padding, child: Text( + key: ValueKey('picker_descriptinon_text_key'), widget.description!, textAlign: TextAlign.center, style: TextStyle( @@ -242,6 +249,7 @@ class _PickerState extends State> { if (widget.isWrapped) { return PickerWrapperWidget( + key: ValueKey('picker_wrapper_widget_key'), hasTitle: widget.title?.isNotEmpty ?? false, children: [content], ); @@ -260,6 +268,7 @@ class _PickerState extends State> { color: Theme.of(context).extension()!.dividerColor, child: widget.isGridView ? GridView.builder( + key: ValueKey('picker_items_grid_view_key'), padding: EdgeInsets.zero, controller: controller, shrinkWrap: true, @@ -275,6 +284,7 @@ class _PickerState extends State> { : buildItem(index), ) : ListView.separated( + key: ValueKey('picker_items_list_view_key'), padding: EdgeInsets.zero, controller: controller, shrinkWrap: true, @@ -297,6 +307,7 @@ class _PickerState extends State> { final item = widget.headerEnabled ? filteredItems[index] : items[index]; final tag = item is Currency ? item.tag : null; + final currencyName = item is Currency ? item.name : ''; final icon = _getItemIcon(item); final image = images.isNotEmpty ? filteredImages[index] : icon; @@ -316,6 +327,7 @@ class _PickerState extends State> { children: [ Flexible( child: Text( + key: ValueKey('picker_items_index_${currencyName}_text_key'), widget.displayItem?.call(item) ?? item.toString(), softWrap: true, style: TextStyle( @@ -335,6 +347,7 @@ class _PickerState extends State> { height: 18.0, child: Center( child: Text( + key: ValueKey('picker_items_index_${index}_tag_key'), tag, style: TextStyle( fontSize: 7.0, @@ -358,6 +371,7 @@ class _PickerState extends State> { ); return GestureDetector( + key: ValueKey('picker_items_index_${currencyName}_button_key'), onTap: () { if (widget.closeOnItemSelected) Navigator.of(context).pop(); onItemSelected(item!); @@ -383,6 +397,7 @@ class _PickerState extends State> { final item = items[index]; final tag = item is Currency ? item.tag : null; + final currencyName = item is Currency ? item.name : ''; final icon = _getItemIcon(item); final image = images.isNotEmpty ? images[index] : icon; @@ -390,6 +405,7 @@ class _PickerState extends State> { final isCustomItem = widget.customItemIndex != null && index == widget.customItemIndex; final itemContent = Row( + key: ValueKey('picker_selected_item_row_key'), mainAxisSize: MainAxisSize.max, mainAxisAlignment: widget.mainAxisAlignment, crossAxisAlignment: CrossAxisAlignment.center, @@ -402,6 +418,7 @@ class _PickerState extends State> { children: [ Flexible( child: Text( + key: ValueKey('picker_items_index_${currencyName}_selected_item_text_key'), widget.displayItem?.call(item) ?? item.toString(), softWrap: true, style: TextStyle( @@ -445,6 +462,7 @@ class _PickerState extends State> { ); return GestureDetector( + key: ValueKey('picker_items_index_${currencyName}_selected_item_button_key'), onTap: () { if (widget.closeOnItemSelected) Navigator.of(context).pop(); }, diff --git a/lib/src/widgets/picker_wrapper_widget.dart b/lib/src/widgets/picker_wrapper_widget.dart index f4e52c5cdd..ac863ac5d2 100644 --- a/lib/src/widgets/picker_wrapper_widget.dart +++ b/lib/src/widgets/picker_wrapper_widget.dart @@ -4,7 +4,12 @@ import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/src/widgets/alert_close_button.dart'; class PickerWrapperWidget extends StatelessWidget { - PickerWrapperWidget({required this.children, this.hasTitle = false, this.onClose}); + PickerWrapperWidget({ + required this.children, + this.hasTitle = false, + this.onClose, + super.key, + }); final List children; final bool hasTitle; @@ -29,8 +34,8 @@ class PickerWrapperWidget extends StatelessWidget { final containerBottom = screenCenter - containerCenter; // position the close button right below the search container - closeButtonBottom = closeButtonBottom - - containerBottom + (!hasTitle ? padding : padding / 1.5); + closeButtonBottom = + closeButtonBottom - containerBottom + (!hasTitle ? padding : padding / 1.5); } return AlertBackground( @@ -46,7 +51,11 @@ class PickerWrapperWidget extends StatelessWidget { children: children, ), SizedBox(height: ResponsiveLayoutUtilBase.kPopupSpaceHeight), - AlertCloseButton(bottom: closeButtonBottom, onTap: onClose), + AlertCloseButton( + key: ValueKey('picker_wrapper_close_button_key'), + bottom: closeButtonBottom, + onTap: onClose, + ), ], ), ), diff --git a/lib/src/widgets/search_bar_widget.dart b/lib/src/widgets/search_bar_widget.dart index 45155b3806..6c7c14db28 100644 --- a/lib/src/widgets/search_bar_widget.dart +++ b/lib/src/widgets/search_bar_widget.dart @@ -7,6 +7,7 @@ class SearchBarWidget extends StatelessWidget { required this.searchController, this.hintText, this.borderRadius = 14, + super.key, }); final TextEditingController searchController; From b31bbc912093fdb6fc2927918d6f42c5103733b1 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 27 Jun 2024 10:01:13 +0100 Subject: [PATCH 05/69] test: Successfully create an exchange section --- integration_test/app_test.dart | 16 ++++++++----- .../components/common_checks.dart | 14 +++++++---- .../robots/exchange_page_robot.dart | 23 ++++++++++++++++--- .../robots/new_wallet_type_page_robot.dart | 2 +- .../restore_from_seed_or_key_robot.dart | 10 ++++---- .../robots/setup_pin_code_robot.dart | 20 ++++------------ 6 files changed, 50 insertions(+), 35 deletions(-) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index 1b0277d296..f2737d860d 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -109,9 +109,9 @@ void main() { // setupPinCodeRobot.hasPinCodeWidget(); // setupPinCodeRobot.hasTitle(); // setupPinCodeRobot.hasNumberButtonsVisible(); - - await setupPinCodeRobot.enterPinCode(true); - await setupPinCodeRobot.enterPinCode(false); + final pin = [0, 8, 0, 1]; + await setupPinCodeRobot.enterPinCode(pin, true); + await setupPinCodeRobot.enterPinCode(pin, false); await setupPinCodeRobot.tapSuccessButton(); // ----------- NewWalletType Page ------------- @@ -126,7 +126,7 @@ void main() { // Select a wallet and route to next page await newWalletTypePageRobot.selectWalletType(WalletType.solana); - await newWalletTypePageRobot.onNextButtonTapped(); + await newWalletTypePageRobot.onNextButtonPressed(); // ----------- RestoreFromSeedOrKeys Page ------------- // Confirm initial defaults - Widgets to be displayed etc @@ -139,7 +139,7 @@ void main() { await restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore( 'noble define inflict tackle sweet essence mention bicycle word hard patient ketchup', ); - await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonTapped(); + await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); // ----------- RestoreFromSeedOrKeys Page ------------- // await dashboardPageRobot.isDashboardPage(); @@ -160,10 +160,14 @@ void main() { exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards(); await exchangePageRobot.selectDepositCurrency(CryptoCurrency.btc); + await exchangePageRobot.enterDepositAmount('0.045'); + await exchangePageRobot.enterDepositRefundAddress(); await exchangePageRobot.selectReceiveCurrency(CryptoCurrency.usdtSol); + await exchangePageRobot.enterReceiveAddress('5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm'); - await Future.delayed(Duration(seconds: 10)); + await exchangePageRobot.onExchangeButtonPressed(); + await Future.delayed(Duration(seconds: 15)); }); }); } diff --git a/integration_test/components/common_checks.dart b/integration_test/components/common_checks.dart index 2816b75b94..24cb280b45 100644 --- a/integration_test/components/common_checks.dart +++ b/integration_test/components/common_checks.dart @@ -1,6 +1,3 @@ -// ignore_for_file: public_member_api_docs, sort_constructors_first -import 'dart:io'; - import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -51,7 +48,8 @@ class CommonTestCases { await tester.pumpAndSettle(); } - Future scrollUntilVisible(String childKey, String parentScrollableKey, {double delta = 300}) async { + Future scrollUntilVisible(String childKey, String parentScrollableKey, + {double delta = 300}) async { final scrollableWidget = find.descendant( of: find.byKey(Key(parentScrollableKey)), matching: find.byType(Scrollable), @@ -64,6 +62,14 @@ class CommonTestCases { ); } + Future enterText(String text, String editableTextKey) async { + final editableTextWidget = find.byKey(ValueKey((editableTextKey))); + + await tester.enterText(editableTextWidget, text); + + await tester.pumpAndSettle(); + } + Future defaultSleepTime({int seconds = 2}) async => await Future.delayed(Duration(seconds: seconds)); } diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart index bf034efaa9..2f6780aaea 100644 --- a/integration_test/robots/exchange_page_robot.dart +++ b/integration_test/robots/exchange_page_robot.dart @@ -144,9 +144,26 @@ class ExchangePageRobot { await commonTestCases.defaultSleepTime(); } - Future enterDepositAddress(String depositAddress) async { - final amountTextField = find.byKey(ValueKey('deposit_exchange_card_amount_textfield_key')); + Future enterDepositAmount(String amount) async { + await commonTestCases.enterText(amount, 'deposit_exchange_card_amount_textfield_key'); } - Future enterReceiveAddress(String receiveAddress) async {} + Future enterDepositRefundAddress({String? depositAddress}) async { + ExchangePage exchangePage = tester.widget(find.byType(ExchangePage)); + final exchangeViewModel = exchangePage.exchangeViewModel; + + if (exchangeViewModel.isDepositAddressEnabled && depositAddress != null) { + await commonTestCases.enterText( + depositAddress, 'deposit_exchange_card_editable_address_textfield_key'); + } + } + + Future enterReceiveAddress(String receiveAddress) async { + await commonTestCases.enterText( + receiveAddress, 'receive_exchange_card_editable_address_textfield_key'); + } + + Future onExchangeButtonPressed() async { + await commonTestCases.tapItemByKey('exchange_page_exchange_button_key'); + } } diff --git a/integration_test/robots/new_wallet_type_page_robot.dart b/integration_test/robots/new_wallet_type_page_robot.dart index 8cc49022a5..16525d9818 100644 --- a/integration_test/robots/new_wallet_type_page_robot.dart +++ b/integration_test/robots/new_wallet_type_page_robot.dart @@ -53,7 +53,7 @@ class NewWalletTypePageRobot { await commonTestCases.tapItemByKey('new_wallet_type_${type.name}_button_key'); } - Future onNextButtonTapped() async { + Future onNextButtonPressed() async { await commonTestCases.tapItemByKey('new_wallet_type_next_button_key'); } } diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index 96cde0d892..4c70dfae00 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -51,10 +51,8 @@ class RestoreFromSeedOrKeysPageRobot { } Future enterWalletNameText(String walletName) async { - final walletNameTextField = - find.byKey(ValueKey(('wallet_restore_from_seed_wallet_name_textfield_key'))); - await tester.enterText(walletNameTextField, walletName); - await tester.pumpAndSettle(); + await commonTestCases.enterText( + walletName, 'wallet_restore_from_seed_wallet_name_textfield_key'); } Future selectWalletNameFromAvailableOptions() async { @@ -70,11 +68,11 @@ class RestoreFromSeedOrKeysPageRobot { await Future.delayed(Duration(seconds: 3)); } - Future onPasteSeedPhraseButtonTapped() async { + Future onPasteSeedPhraseButtonPressed() async { await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'); } - Future onRestoreWalletButtonTapped() async { + Future onRestoreWalletButtonPressed() async { await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key'); // await commonTestCases.defaultSleepTime(seconds: 15); } diff --git a/integration_test/robots/setup_pin_code_robot.dart b/integration_test/robots/setup_pin_code_robot.dart index 2caaddc8ac..b915da55bb 100644 --- a/integration_test/robots/setup_pin_code_robot.dart +++ b/integration_test/robots/setup_pin_code_robot.dart @@ -1,7 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../components/common_checks.dart'; @@ -36,25 +35,16 @@ class SetupPinCodeRobot { } Future pushPinButton(int index) async { - final button = find.byKey(ValueKey('pin_code_button_${index}_key')); - await tester.tap(button); - await tester.pumpAndSettle(); + await commonTestCases.tapItemByKey('pin_code_button_${index}_key'); } - Future enterPinCode(bool isFirstEntry) async { + Future enterPinCode(List pinCode, bool isFirstEntry) async { final PinCodeState pinCodeState = tester.state(find.byType(PinCodeWidget)); - tester.printToConsole(pinCodeState.pin); - await pushPinButton(0); - expect(pinCodeState.pin, '0'); - - await pushPinButton(8); - expect(pinCodeState.pin, '08'); - - await pushPinButton(0); - expect(pinCodeState.pin, '080'); + for (int pin in pinCode) { + await pushPinButton(pin); + } - await pushPinButton(1); // the state is cleared once it get's to the last entry expect(pinCodeState.pin, isFirstEntry ? '' : '0801'); From 2038154b32eb479e5d361a8cce4399e597a5a8e3 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 28 Jun 2024 15:03:00 +0100 Subject: [PATCH 06/69] feat: Implement flow up to sending section --- .gitignore | 1 + integration_test/app_test.dart | 139 ++++++-------- .../components/common_checks.dart | 16 ++ integration_test/robots/auth_page_robot.dart | 30 +++ .../robots/exchange_confirm_page_robot.dart | 43 +++++ .../robots/exchange_page_robot.dart | 181 ++++++++++++++++-- .../robots/exchange_trade_page_robot.dart | 71 +++++++ .../robots/pin_code_widget_robot.dart | 43 +++++ .../restore_from_seed_or_key_robot.dart | 4 +- .../robots/setup_pin_code_robot.dart | 40 +--- .../robots/welcome_page_robot.dart | 2 - lib/src/screens/exchange/exchange_page.dart | 2 + .../exchange_trade/exchange_confirm_page.dart | 2 + .../exchange_trade/exchange_trade_page.dart | 11 +- .../exchange_trade/information_page.dart | 3 +- .../send/widgets/confirm_sending_alert.dart | 59 +++--- lib/src/widgets/alert_with_one_action.dart | 3 +- lib/src/widgets/base_alert_dialog.dart | 6 + 18 files changed, 500 insertions(+), 156 deletions(-) create mode 100644 integration_test/robots/auth_page_robot.dart create mode 100644 integration_test/robots/exchange_confirm_page_robot.dart create mode 100644 integration_test/robots/exchange_trade_page_robot.dart create mode 100644 integration_test/robots/pin_code_widget_robot.dart diff --git a/.gitignore b/.gitignore index d0952ca98e..95bb954b9c 100644 --- a/.gitignore +++ b/.gitignore @@ -166,3 +166,4 @@ macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_256.png macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_512.png macos/Runner/Assets.xcassets/AppIcon.appiconset/app_icon_1024.png macos/Runner/Configs/AppInfo.xcconfig +integration_test/playground.dart diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index f2737d860d..d7e4393fa5 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -5,14 +5,18 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'robots/auth_page_robot.dart'; import 'robots/dashboard_page_robot.dart'; import 'robots/disclaimer_page_robot.dart'; +import 'robots/exchange_confirm_page_robot.dart'; import 'robots/exchange_page_robot.dart'; +import 'robots/exchange_trade_page_robot.dart'; import 'robots/new_wallet_type_page_robot.dart'; import 'robots/restore_from_seed_or_key_robot.dart'; import 'robots/restore_options_page_robot.dart'; import 'robots/setup_pin_code_robot.dart'; import 'robots/welcome_page_robot.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; Future restoreFlutterError() async { final originalOnError = FlutterError.onError!; @@ -34,140 +38,117 @@ void main() { RestoreFromSeedOrKeysPageRobot restoreFromSeedOrKeysPageRobot; DashboardPageRobot dashboardPageRobot; ExchangePageRobot exchangePageRobot; + ExchangeConfirmPageRobot exchangeConfirmPageRobot; + AuthPageRobot authPageRobot; + ExchangeTradePageRobot exchangeTradePageRobot; group('Startup Test', () { testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL', (tester) async { - disclaimerPageRobot = DisclaimerPageRobot(tester); + authPageRobot = AuthPageRobot(tester); welcomePageRobot = WelcomePageRobot(tester); + exchangePageRobot = ExchangePageRobot(tester); setupPinCodeRobot = SetupPinCodeRobot(tester); - restoreOptionsPageRobot = RestoreOptionsPageRobot(tester); + dashboardPageRobot = DashboardPageRobot(tester); + disclaimerPageRobot = DisclaimerPageRobot(tester); + exchangeTradePageRobot = ExchangeTradePageRobot(tester); newWalletTypePageRobot = NewWalletTypePageRobot(tester); + restoreOptionsPageRobot = RestoreOptionsPageRobot(tester); + exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester); restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester); - dashboardPageRobot = DashboardPageRobot(tester); - exchangePageRobot = ExchangePageRobot(tester); + + final pin = [0, 8, 0, 1]; + + String testAmount = '8'; + CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; + CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; + + WalletType testWalletType = WalletType.solana; + String testWalletName = 'Integrated Testing Wallet'; + String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L'; await app.main(); await tester.pumpAndSettle(); // --------- Disclaimer Page ------------ - // Confirm initial defaults - // await disclaimerPageRobot.isDisclaimerPage(); - // disclaimerPageRobot.hasCheckIcon(false); - // disclaimerPageRobot.hasDisclaimerCheckbox(); - // Tap checkbox to accept disclaimer await disclaimerPageRobot.tapDisclaimerCheckbox(); - // Confirm that page has been updated with the check mark icon in checkbox - // disclaimerPageRobot.hasCheckIcon(true); - // Tap accept button await disclaimerPageRobot.tapAcceptButton(); - tester.printToConsole('Routing to Welcome Page'); // --------- Welcome Page --------------- - // Confirm initial defaults - // await welcomePageRobot.isWelcomePage(); - // welcomePageRobot.confirmActionButtonsDisplay(); - - // Confirm routing to Create Wallet Page works - // await welcomePageRobot.navigateToCreateNewWalletPage(); - // await welcomePageRobot.backAndVerify(); - - // Confirm routing to Restore Wallet Page works - // await welcomePageRobot.navigateToRestoreWalletPage(); - // await welcomePageRobot.backAndVerify(); - - // Route to restore wallet to continue flow await welcomePageRobot.navigateToRestoreWalletPage(); - tester.printToConsole('Routing to Restore Wallet Page'); // ----------- Restore Options Page ----------- - // Confirm initial defaults - // await restoreOptionsPageRobot.isRestoreOptionsPage(); - // restoreOptionsPageRobot.hasRestoreOptionsButton(); - - // Confirm routing to Restore from seeds Page works - // await restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); - // await restoreOptionsPageRobot.backAndVerify(); - - // Confirm routing to Restore from backup Page works - // await restoreOptionsPageRobot.navigateToRestoreFromBackupPage(); - // await restoreOptionsPageRobot.backAndVerify(); - - // Confirm routing to Restore from hardware wallet Page works - // await restoreOptionsPageRobot.navigateToRestoreFromHardwareWalletPage(); - // await restoreOptionsPageRobot.backAndVerify(); - // Route to restore from seeds page to continue flow await restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); // ----------- SetupPinCode Page ------------- // Confirm initial defaults - Widgets to be displayed etc - // await setupPinCodeRobot.isSetupPinCodePage(); - // setupPinCodeRobot.hasPinCodeWidget(); - // setupPinCodeRobot.hasTitle(); - // setupPinCodeRobot.hasNumberButtonsVisible(); - final pin = [0, 8, 0, 1]; + await setupPinCodeRobot.isSetupPinCodePage(); + await setupPinCodeRobot.enterPinCode(pin, true); await setupPinCodeRobot.enterPinCode(pin, false); await setupPinCodeRobot.tapSuccessButton(); // ----------- NewWalletType Page ------------- - // Confirm initial defaults - Widgets to be displayed etc - // await newWalletTypePageRobot.isNewWalletTypePage(); - // newWalletTypePageRobot.displaysCorrectTitle(false); - // newWalletTypePageRobot.displaysCorrectImage(ThemeType.dark); - // newWalletTypePageRobot.hasWalletTypeForm(); - // Confirm scroll behaviour works properly await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.haven); // Select a wallet and route to next page - await newWalletTypePageRobot.selectWalletType(WalletType.solana); + await newWalletTypePageRobot.selectWalletType(testWalletType); await newWalletTypePageRobot.onNextButtonPressed(); // ----------- RestoreFromSeedOrKeys Page ------------- - // Confirm initial defaults - Widgets to be displayed etc - // await restoreFromSeedOrKeysPageRobot.isRestoreFromSeedKeyPage(); - // await restoreFromSeedOrKeysPageRobot.confirmViewComponentsDisplayProperlyPerPageView(); - // restoreFromSeedOrKeysPageRobot.confirmRestoreButtonDisplays(); - // restoreFromSeedOrKeysPageRobot.confirmAdvancedSettingButtonDisplays(); - - await restoreFromSeedOrKeysPageRobot.enterWalletNameText('Integrated Testing Wallet'); - await restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore( - 'noble define inflict tackle sweet essence mention bicycle word hard patient ketchup', - ); + await restoreFromSeedOrKeysPageRobot.enterWalletNameText(testWalletName); + await restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.seeds); await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); // ----------- RestoreFromSeedOrKeys Page ------------- - // await dashboardPageRobot.isDashboardPage(); - // dashboardPageRobot.confirmServiceUpdateButtonDisplays(); - // dashboardPageRobot.confirmMenuButtonDisplays(); - // dashboardPageRobot.confirmSyncIndicatorButtonDisplays(); - // await dashboardPageRobot.confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType.solana); - await dashboardPageRobot.navigateToExchangePage(); - await Future.delayed(Duration(seconds: 2)); // ----------- Exchange Page ------------- await exchangePageRobot.isExchangePage(); - exchangePageRobot.hasTitle(); exchangePageRobot.hasResetButton(); await exchangePageRobot.displayBothExchangeCards(); exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards(); exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards(); - await exchangePageRobot.selectDepositCurrency(CryptoCurrency.btc); - await exchangePageRobot.enterDepositAmount('0.045'); - await exchangePageRobot.enterDepositRefundAddress(); + await exchangePageRobot.selectDepositCurrency(testDepositCurrency); + await exchangePageRobot.selectReceiveCurrency(testReceiveCurrency); - await exchangePageRobot.selectReceiveCurrency(CryptoCurrency.usdtSol); - await exchangePageRobot.enterReceiveAddress('5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm'); + await exchangePageRobot.enterDepositAmount(testAmount); + await exchangePageRobot.enterDepositRefundAddress(depositAddress: testWalletAddress); + + await exchangePageRobot.enterReceiveAddress(testWalletAddress); await exchangePageRobot.onExchangeButtonPressed(); - await Future.delayed(Duration(seconds: 15)); + + await exchangePageRobot.handleErrors(testAmount); + + final onAuthPage = authPageRobot.onAuthPage(); + if (onAuthPage) { + await authPageRobot.enterPinCode(pin, false); + } + + // ----------- Exchange Confirm Page ------------- + await exchangeConfirmPageRobot.isExchangeConfirmPage(); + + exchangeConfirmPageRobot.confirmComponentsOfTradeDisplayProperly(); + await exchangeConfirmPageRobot.confirmCopyTradeIdToClipBoardWorksProperly(); + await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); + + // ----------- Exchange Trade Page ------------- + await exchangeTradePageRobot.isExchangeTradePage(); + exchangeTradePageRobot.hasInformationDialog(); + await exchangeTradePageRobot.onGotItButtonPressed(); + + await exchangeTradePageRobot.onConfirmSendingButtonPressed(); + + // await exchangeTradePageRobot.handleSendSuccessOrFailure(); + + // await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed(); }); }); } diff --git a/integration_test/components/common_checks.dart b/integration_test/components/common_checks.dart index 24cb280b45..a5335fe8ee 100644 --- a/integration_test/components/common_checks.dart +++ b/integration_test/components/common_checks.dart @@ -55,6 +55,10 @@ class CommonTestCases { matching: find.byType(Scrollable), ); + final isAlreadyVisibile = isWidgetVisible(find.byKey(ValueKey(childKey))); + + if (isAlreadyVisibile) return; + await tester.scrollUntilVisible( find.byKey(ValueKey(childKey)), delta, @@ -62,6 +66,18 @@ class CommonTestCases { ); } + bool isWidgetVisible(Finder finder) { + try { + final Element element = finder.evaluate().single; + final RenderBox renderBox = element.renderObject as RenderBox; + return renderBox.paintBounds + .shift(renderBox.localToGlobal(Offset.zero)) + .overlaps(tester.binding.renderViews.first.paintBounds); + } catch (e) { + return false; + } + } + Future enterText(String text, String editableTextKey) async { final editableTextWidget = find.byKey(ValueKey((editableTextKey))); diff --git a/integration_test/robots/auth_page_robot.dart b/integration_test/robots/auth_page_robot.dart new file mode 100644 index 0000000000..e45a82c0c3 --- /dev/null +++ b/integration_test/robots/auth_page_robot.dart @@ -0,0 +1,30 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/auth/auth_page.dart'; +import 'package:flutter/widgets.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; +import 'pin_code_widget_robot.dart'; + +class AuthPageRobot extends PinCodeWidgetRobot { + AuthPageRobot(this.tester) + : commonTestCases = CommonTestCases(tester), + super(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + bool onAuthPage() { + final hasPinButtons = find.byKey(ValueKey('pin_code_button_3_key')); + final hasPin = hasPinButtons.tryEvaluate(); + return hasPin; + } + + Future isAuthPage() async { + await commonTestCases.isSpecificPage(); + } + + void hasTitle() { + commonTestCases.hasText(S.current.setup_pin); + } +} diff --git a/integration_test/robots/exchange_confirm_page_robot.dart b/integration_test/robots/exchange_confirm_page_robot.dart new file mode 100644 index 0000000000..dd2ec68d5b --- /dev/null +++ b/integration_test/robots/exchange_confirm_page_robot.dart @@ -0,0 +1,43 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dart'; +import 'package:flutter/services.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; + +class ExchangeConfirmPageRobot { + ExchangeConfirmPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isExchangeConfirmPage() async { + await commonTestCases.isSpecificPage(); + } + + void confirmComponentsOfTradeDisplayProperly() { + final ExchangeConfirmPage exchangeConfirmPage = tester.widget(find.byType(ExchangeConfirmPage)); + final trade = exchangeConfirmPage.trade; + + commonTestCases.hasText(trade.id); + commonTestCases.hasText('${trade.provider.title} ${S.current.trade_id}'); + + commonTestCases.hasValueKey('exchange_confirm_page_saved_id_button_key'); + commonTestCases.hasValueKey('exchange_confirm_page_copy_to_clipboard_button_key'); + } + + Future confirmCopyTradeIdToClipBoardWorksProperly() async { + final ExchangeConfirmPage exchangeConfirmPage = tester.widget(find.byType(ExchangeConfirmPage)); + final trade = exchangeConfirmPage.trade; + + await commonTestCases.tapItemByKey('exchange_confirm_page_copy_to_clipboard_button_key'); + + ClipboardData? clipboardData = await Clipboard.getData('text/plain'); + + expect(clipboardData?.text, trade.id); + } + + Future onSavedTradeIdButtonPressed() async { + await commonTestCases.tapItemByKey('exchange_confirm_page_saved_id_button_key'); + } +} diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart index 2f6780aaea..895845c077 100644 --- a/integration_test/robots/exchange_page_robot.dart +++ b/integration_test/robots/exchange_page_robot.dart @@ -1,10 +1,8 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/exchange/exchange_page.dart'; -import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; -import 'package:cake_wallet/src/screens/exchange/widgets/exchange_card.dart'; import 'package:cake_wallet/src/screens/exchange/widgets/present_provider_picker.dart'; import 'package:cw_core/crypto_currency.dart'; -import 'package:flutter/material.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import '../components/common_checks.dart'; @@ -17,10 +15,7 @@ class ExchangePageRobot { Future isExchangePage() async { await commonTestCases.isSpecificPage(); - } - - void hasTitle() { - // commonTestCases.hasText(S.current.exchange); + await commonTestCases.defaultSleepTime(); } void hasResetButton() { @@ -55,6 +50,9 @@ class ExchangePageRobot { commonTestCases.hasValueKey('${depositCardPrefix}_currency_picker_button_key'); commonTestCases.hasValueKey('${depositCardPrefix}_selected_currency_text_key'); commonTestCases.hasValueKey('${depositCardPrefix}_amount_textfield_key'); + + exchangePage.depositKey.currentState!.changeLimits(min: '0.1'); + commonTestCases.hasValueKey('${depositCardPrefix}_min_limit_text_key'); final initialCurrency = exchangeViewModel.depositCurrency; @@ -115,6 +113,16 @@ class ExchangePageRobot { await commonTestCases.tapItemByKey(currencyPickerKey); commonTestCases.hasValueKey(currencyPickerDialogKey); + ExchangePage exchangePage = tester.widget(find.byType(ExchangePage)); + final exchangeViewModel = exchangePage.exchangeViewModel; + + if (depositCurrency == exchangeViewModel.depositCurrency) { + await commonTestCases.defaultSleepTime(); + await commonTestCases + .tapItemByKey('picker_items_index_${depositCurrency.name}_selected_item_button_key'); + return; + } + await commonTestCases.scrollUntilVisible( 'picker_items_index_${depositCurrency.name}_button_key', 'picker_scrollbar_key', @@ -122,7 +130,6 @@ class ExchangePageRobot { await commonTestCases.defaultSleepTime(); await commonTestCases.tapItemByKey('picker_items_index_${depositCurrency.name}_button_key'); - await commonTestCases.defaultSleepTime(); } Future selectReceiveCurrency(CryptoCurrency receiveCurrency) async { @@ -131,9 +138,17 @@ class ExchangePageRobot { final currencyPickerDialogKey = '${receivePrefix}_currency_picker_dialog_button_key'; await commonTestCases.tapItemByKey(currencyPickerKey); - commonTestCases.hasValueKey(currencyPickerDialogKey); + ExchangePage exchangePage = tester.widget(find.byType(ExchangePage)); + final exchangeViewModel = exchangePage.exchangeViewModel; + + if (receiveCurrency == exchangeViewModel.receiveCurrency) { + await commonTestCases + .tapItemByKey('picker_items_index_${receiveCurrency.name}_selected_item_button_key'); + return; + } + await commonTestCases.scrollUntilVisible( 'picker_items_index_${receiveCurrency.name}_button_key', 'picker_scrollbar_key', @@ -141,7 +156,6 @@ class ExchangePageRobot { await commonTestCases.defaultSleepTime(); await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key'); - await commonTestCases.defaultSleepTime(); } Future enterDepositAmount(String amount) async { @@ -160,10 +174,155 @@ class ExchangePageRobot { Future enterReceiveAddress(String receiveAddress) async { await commonTestCases.enterText( - receiveAddress, 'receive_exchange_card_editable_address_textfield_key'); + receiveAddress, + 'receive_exchange_card_editable_address_textfield_key', + ); + await commonTestCases.defaultSleepTime(); } Future onExchangeButtonPressed() async { await commonTestCases.tapItemByKey('exchange_page_exchange_button_key'); + await commonTestCases.defaultSleepTime(); + } + + bool hasMaxLimitError() { + final maxErrorText = find.text(S.current.error_text_input_above_maximum_limit); + + bool hasMaxError = maxErrorText.tryEvaluate(); + + return hasMaxError; + } + + bool hasMinLimitError() { + final minErrorText = find.text(S.current.error_text_input_below_minimum_limit); + + bool hasMinError = minErrorText.tryEvaluate(); + + return hasMinError; + } + + bool hasTradeCreationFailureError() { + final tradeCreationFailureDialogButton = + find.byKey(ValueKey('exchange_page_trade_creation_failure_dialog_button_key')); + + bool hasTradeCreationFailure = tradeCreationFailureDialogButton.tryEvaluate(); + tester.printToConsole('Trade not created error: $hasTradeCreationFailure'); + return hasTradeCreationFailure; + } + + Future onTradeCreationFailureDialogButtonPressed() async { + await commonTestCases.tapItemByKey('exchange_page_trade_creation_failure_dialog_button_key'); + } + + /// Handling Trade Failure Errors or errors shown through the Failure Dialog. + /// + /// Simulating the user's flow and response when this error comes up. + /// Examples are: + /// - No provider can handle this trade error, + /// - Trade amount below limit error. + Future _handleTradeCreationFailureErrors() async { + bool isTradeCreationFailure = false; + + isTradeCreationFailure = hasTradeCreationFailureError(); + + int maxRetries = 20; + int retries = 0; + + while (isTradeCreationFailure && retries < maxRetries) { + await tester.pump(); + + await onTradeCreationFailureDialogButtonPressed(); + + await commonTestCases.defaultSleepTime(seconds: 5); + + await onExchangeButtonPressed(); + + isTradeCreationFailure = hasTradeCreationFailureError(); + retries++; + } + } + + /// Handles the min limit error. + /// + /// Simulates the user's flow and response when it comes up. + /// + /// Has a max retry of 20 times. + Future _handleMinLimitError(String initialAmount) async { + bool isMinLimitError = false; + + isMinLimitError = hasMinLimitError(); + + int amount; + + amount = int.parse(initialAmount); + + int maxRetries = 20; + int retries = 0; + + while (isMinLimitError && retries < maxRetries) { + amount++; + tester.printToConsole('Amount: $amount'); + + enterDepositAmount(amount.toString()); + + await commonTestCases.defaultSleepTime(); + + await onExchangeButtonPressed(); + + isMinLimitError = hasMinLimitError(); + + retries++; + } + + if (retries >= maxRetries) { + tester.printToConsole('Max retries reached for minLimit Error. Exiting loop.'); + } + } + + /// Handles the max limit error. + /// + /// Simulates the user's flow and response when it comes up. + /// + /// Has a max retry of 20 times. + Future _handleMaxLimitError(String initialAmount) async { + bool isMaxLimitError = false; + + isMaxLimitError = hasMaxLimitError(); + + int amount; + + amount = int.parse(initialAmount); + + int maxRetries = 20; + int retries = 0; + + while (isMaxLimitError && retries < maxRetries) { + amount++; + tester.printToConsole('Amount: $amount'); + + enterDepositAmount(amount.toString()); + + await commonTestCases.defaultSleepTime(); + + await onExchangeButtonPressed(); + + isMaxLimitError = hasMaxLimitError(); + + retries++; + } + + if (retries >= maxRetries) { + tester.printToConsole('Max retries reached for maxLimit Error. Exiting loop.'); + } + } + + Future handleErrors(String initialAmount) async { + await _handleMinLimitError(initialAmount); + + await _handleMaxLimitError(initialAmount); + + await _handleTradeCreationFailureErrors(); + + await commonTestCases.defaultSleepTime(); } } diff --git a/integration_test/robots/exchange_trade_page_robot.dart b/integration_test/robots/exchange_trade_page_robot.dart new file mode 100644 index 0000000000..49848f1a56 --- /dev/null +++ b/integration_test/robots/exchange_trade_page_robot.dart @@ -0,0 +1,71 @@ +import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; + +class ExchangeTradePageRobot { + ExchangeTradePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isExchangeTradePage() async { + await commonTestCases.isSpecificPage(); + } + + void hasInformationDialog() { + commonTestCases.hasValueKey('information_page_dialog_key'); + } + + Future onGotItButtonPressed() async { + await commonTestCases.tapItemByKey('information_page_got_it_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future onConfirmSendingButtonPressed() async { + await commonTestCases.tapItemByKey('exchange_trade_page_confirm_sending_button_key'); + } + + Future onSendButtonOnConfirmSendingDialogPressed() async { + await commonTestCases + .tapItemByKey('exchange_trade_page_confirm_sending_dialog_send_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future onCancelButtonOnConfirmSendingDialogPressed() async { + await commonTestCases + .tapItemByKey('exchange_trade_page_confirm_sending_dialog_cancel_button_key'); + await commonTestCases.defaultSleepTime(); + } + + bool hasErrorWhileSending() { + final errorDialog = find.byKey(ValueKey('exchange_trade_page_send_failure_dialog_button_key')); + bool hasError = errorDialog.tryEvaluate(); + return hasError; + } + + Future onSendFailureDialogButtonPressed() async { + await commonTestCases.tapItemByKey('exchange_trade_page_send_failure_dialog_button_key'); + await commonTestCases.defaultSleepTime(); + } + + // Future handleSendSuccessOrFailure() async { + // bool hasError = false; + + // hasError = hasErrorWhileSending(); + + // if (hasError) { + // tester.printToConsole('hasError: $hasError'); + // await onSendFailureDialogButtonPressed(); + // tester.printToConsole('Failure button tapped'); + // await onConfirmSendingButtonPressed(); + // tester.printToConsole('Confirm sending tapped'); + // await handleSendSuccessOrFailure(); + // tester.printToConsole('Let\'s go'); + // } else { + // await onSendButtonOnConfirmSendingDialogPressed(); + // return; + // } + // } +} diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart new file mode 100644 index 0000000000..7fa0bcd9cc --- /dev/null +++ b/integration_test/robots/pin_code_widget_robot.dart @@ -0,0 +1,43 @@ +import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_checks.dart'; + +class PinCodeWidgetRobot { + PinCodeWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + void hasPinCodeWidget() { + final pinCodeWidget = find.bySubtype(); + expect(pinCodeWidget, findsOneWidget); + } + + void hasNumberButtonsVisible() { + // Confirmation for buttons 1-9 + for (var i = 1; i < 10; i++) { + commonTestCases.hasValueKey('pin_code_button_${i}_key'); + } + + // Confirmation for 0 button + commonTestCases.hasValueKey('pin_code_button_0_key'); + } + + Future pushPinButton(int index) async { + await commonTestCases.tapItemByKey('pin_code_button_${index}_key'); + } + + Future enterPinCode(List pinCode, bool isFirstEntry) async { + // final PinCodeWidget pinCodeState = tester.widget(find.bySubtype()); + + for (int pin in pinCode) { + await pushPinButton(pin); + } + + // the state is cleared once it get's to the last entry + // expect(pinCodeState. .pin, isFirstEntry ? '' : '0801'); + + await commonTestCases.defaultSleepTime(); + } +} diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index 4c70dfae00..28f95df83a 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -1,7 +1,6 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import '../components/common_checks.dart'; @@ -65,7 +64,6 @@ class RestoreFromSeedOrKeysPageRobot { seedTextState.widget.controller.text = text; await tester.pumpAndSettle(); - await Future.delayed(Duration(seconds: 3)); } Future onPasteSeedPhraseButtonPressed() async { @@ -74,6 +72,6 @@ class RestoreFromSeedOrKeysPageRobot { Future onRestoreWalletButtonPressed() async { await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key'); - // await commonTestCases.defaultSleepTime(seconds: 15); + await commonTestCases.defaultSleepTime(); } } diff --git a/integration_test/robots/setup_pin_code_robot.dart b/integration_test/robots/setup_pin_code_robot.dart index b915da55bb..3f3d81c164 100644 --- a/integration_test/robots/setup_pin_code_robot.dart +++ b/integration_test/robots/setup_pin_code_robot.dart @@ -1,12 +1,14 @@ import 'package:cake_wallet/generated/i18n.dart'; -import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; import 'package:flutter_test/flutter_test.dart'; import '../components/common_checks.dart'; +import 'pin_code_widget_robot.dart'; -class SetupPinCodeRobot { - SetupPinCodeRobot(this.tester) : commonTestCases = CommonTestCases(tester); +class SetupPinCodeRobot extends PinCodeWidgetRobot { + SetupPinCodeRobot(this.tester) + : commonTestCases = CommonTestCases(tester), + super(tester); final WidgetTester tester; late CommonTestCases commonTestCases; @@ -15,42 +17,10 @@ class SetupPinCodeRobot { await commonTestCases.isSpecificPage(); } - void hasPinCodeWidget() { - final pinCodeWidget = find.byType(PinCodeWidget); - expect(pinCodeWidget, findsOneWidget); - } - void hasTitle() { commonTestCases.hasText(S.current.setup_pin); } - void hasNumberButtonsVisible() { - // Confirmation for buttons 1-9 - for (var i = 1; i < 10; i++) { - commonTestCases.hasValueKey('pin_code_button_${i}_key'); - } - - // Confirmation for 0 button - commonTestCases.hasValueKey('pin_code_button_0_key'); - } - - Future pushPinButton(int index) async { - await commonTestCases.tapItemByKey('pin_code_button_${index}_key'); - } - - Future enterPinCode(List pinCode, bool isFirstEntry) async { - final PinCodeState pinCodeState = tester.state(find.byType(PinCodeWidget)); - - for (int pin in pinCode) { - await pushPinButton(pin); - } - - // the state is cleared once it get's to the last entry - expect(pinCodeState.pin, isFirstEntry ? '' : '0801'); - - await commonTestCases.defaultSleepTime(); - } - Future tapSuccessButton() async { await commonTestCases.tapItemByKey('setup_pin_code_success_button_key'); await commonTestCases.defaultSleepTime(); diff --git a/integration_test/robots/welcome_page_robot.dart b/integration_test/robots/welcome_page_robot.dart index 12a752f0c0..4943666d00 100644 --- a/integration_test/robots/welcome_page_robot.dart +++ b/integration_test/robots/welcome_page_robot.dart @@ -1,5 +1,3 @@ -import 'dart:io'; - import 'package:cake_wallet/src/screens/welcome/welcome_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; diff --git a/lib/src/screens/exchange/exchange_page.dart b/lib/src/screens/exchange/exchange_page.dart index fc4d6cbf7a..5d0fdb5da4 100644 --- a/lib/src/screens/exchange/exchange_page.dart +++ b/lib/src/screens/exchange/exchange_page.dart @@ -442,6 +442,8 @@ class ExchangePage extends BasePage { context: context, builder: (BuildContext context) { return AlertWithOneAction( + key: ValueKey('exchange_page_trade_creation_failure_dialog_key'), + buttonKey: ValueKey('exchange_page_trade_creation_failure_dialog_button_key'), alertTitle: S.of(context).provider_error(state.title), alertContent: state.error, buttonText: S.of(context).ok, diff --git a/lib/src/screens/exchange_trade/exchange_confirm_page.dart b/lib/src/screens/exchange_trade/exchange_confirm_page.dart index 8070febdf7..8a3365cff7 100644 --- a/lib/src/screens/exchange_trade/exchange_confirm_page.dart +++ b/lib/src/screens/exchange_trade/exchange_confirm_page.dart @@ -82,6 +82,7 @@ class ExchangeConfirmPage extends BasePage { padding: EdgeInsets.fromLTRB(10, 0, 10, 10), child: Builder( builder: (context) => PrimaryButton( + key: ValueKey('exchange_confirm_page_copy_to_clipboard_button_key'), onPressed: () { Clipboard.setData(ClipboardData(text: trade.id)); showBar( @@ -115,6 +116,7 @@ class ExchangeConfirmPage extends BasePage { ], )), PrimaryButton( + key: ValueKey('exchange_confirm_page_saved_id_button_key'), onPressed: () => Navigator.of(context) .pushReplacementNamed(Routes.exchangeTrade), text: S.of(context).saved_the_trade_id, diff --git a/lib/src/screens/exchange_trade/exchange_trade_page.dart b/lib/src/screens/exchange_trade/exchange_trade_page.dart index 4d3334f9fb..d56b44f1e3 100644 --- a/lib/src/screens/exchange_trade/exchange_trade_page.dart +++ b/lib/src/screens/exchange_trade/exchange_trade_page.dart @@ -39,7 +39,9 @@ void showInformation( showPopUp( context: context, - builder: (_) => InformationPage(information: information)); + builder: (_) => InformationPage( + key: ValueKey('information_page_dialog_key'), + information: information)); } class ExchangeTradePage extends BasePage { @@ -215,6 +217,7 @@ class ExchangeTradeState extends State { return widget.exchangeTradeViewModel.isSendable && !(sendingState is TransactionCommitted) ? LoadingPrimaryButton( + key: ValueKey('exchange_trade_page_confirm_sending_button_key'), isDisabled: trade.inputAddress == null || trade.inputAddress!.isEmpty, isLoading: sendingState is IsExecutingState, @@ -241,6 +244,8 @@ class ExchangeTradeState extends State { context: context, builder: (BuildContext popupContext) { return AlertWithOneAction( + key: ValueKey('exchange_trade_page_send_failure_dialog_key'), + buttonKey: ValueKey('exchange_trade_page_send_failure_dialog_button_key'), alertTitle: S.of(popupContext).error, alertContent: state.error, buttonText: S.of(popupContext).ok, @@ -255,6 +260,10 @@ class ExchangeTradeState extends State { context: context, builder: (BuildContext popupContext) { return ConfirmSendingAlert( + key: ValueKey('exchange_trade_page_confirm_sending_dialog_key'), + alertLeftActionButtonKey: ValueKey('exchange_trade_page_confirm_sending_dialog_cancel_button_key'), + alertRightActionButtonKey: + ValueKey('exchange_trade_page_confirm_sending_dialog_send_button_key'), alertTitle: S.of(popupContext).confirm_sending, amount: S.of(popupContext).send_amount, amountValue: widget.exchangeTradeViewModel.sendViewModel diff --git a/lib/src/screens/exchange_trade/information_page.dart b/lib/src/screens/exchange_trade/information_page.dart index eed124b97c..114a4824d9 100644 --- a/lib/src/screens/exchange_trade/information_page.dart +++ b/lib/src/screens/exchange_trade/information_page.dart @@ -10,7 +10,7 @@ import 'package:cake_wallet/src/widgets/alert_background.dart'; import 'package:cake_wallet/themes/extensions/menu_theme.dart'; class InformationPage extends StatelessWidget { - InformationPage({required this.information}); + InformationPage({required this.information, super.key}); final String information; @@ -47,6 +47,7 @@ class InformationPage extends StatelessWidget { Padding( padding: EdgeInsets.fromLTRB(10, 0, 10, 10), child: PrimaryButton( + key: ValueKey('information_page_got_it_button_key'), onPressed: () => Navigator.of(context).pop(), text: S.of(context).got_it, color: Theme.of(context).extension()!.buttonBackgroundColor, diff --git a/lib/src/screens/send/widgets/confirm_sending_alert.dart b/lib/src/screens/send/widgets/confirm_sending_alert.dart index 3af1c3f8cb..0b0fecfd4b 100644 --- a/lib/src/screens/send/widgets/confirm_sending_alert.dart +++ b/lib/src/screens/send/widgets/confirm_sending_alert.dart @@ -8,29 +8,33 @@ import 'package:cake_wallet/src/widgets/cake_scrollbar.dart'; import 'package:flutter/scheduler.dart'; class ConfirmSendingAlert extends BaseAlertDialog { - ConfirmSendingAlert( - {required this.alertTitle, - this.paymentId, - this.paymentIdValue, - this.expirationTime, - required this.amount, - required this.amountValue, - required this.fiatAmountValue, - required this.fee, - this.feeRate, - required this.feeValue, - required this.feeFiatAmount, - required this.outputs, - required this.leftButtonText, - required this.rightButtonText, - required this.actionLeftButton, - required this.actionRightButton, - this.alertBarrierDismissible = true, - this.alertLeftActionButtonTextColor, - this.alertRightActionButtonTextColor, - this.alertLeftActionButtonColor, - this.alertRightActionButtonColor, - this.onDispose}); + ConfirmSendingAlert({ + required this.alertTitle, + this.paymentId, + this.paymentIdValue, + this.expirationTime, + required this.amount, + required this.amountValue, + required this.fiatAmountValue, + required this.fee, + this.feeRate, + required this.feeValue, + required this.feeFiatAmount, + required this.outputs, + required this.leftButtonText, + required this.rightButtonText, + required this.actionLeftButton, + required this.actionRightButton, + this.alertBarrierDismissible = true, + this.alertLeftActionButtonTextColor, + this.alertRightActionButtonTextColor, + this.alertLeftActionButtonColor, + this.alertRightActionButtonColor, + this.onDispose, + this.alertLeftActionButtonKey, + this.alertRightActionButtonKey, + Key? key, + }); final String alertTitle; final String? paymentId; @@ -54,6 +58,8 @@ class ConfirmSendingAlert extends BaseAlertDialog { final Color? alertLeftActionButtonColor; final Color? alertRightActionButtonColor; final Function? onDispose; + final Key? alertRightActionButtonKey; + final Key? alertLeftActionButtonKey; @override String get titleText => alertTitle; @@ -88,6 +94,12 @@ class ConfirmSendingAlert extends BaseAlertDialog { @override Color? get rightActionButtonColor => alertRightActionButtonColor; + @override + Key? get leftActionButtonKey => alertLeftActionButtonKey; + + @override + Key? get rightActionButtonKey => alertLeftActionButtonKey; + @override Widget content(BuildContext context) => ConfirmSendingAlertContent( paymentId: paymentId, @@ -279,6 +291,7 @@ class ConfirmSendingAlertContentState extends State crossAxisAlignment: CrossAxisAlignment.end, children: [ Text( + key: ValueKey('confirm_sending_dialog_amount_text_value_key'), amountValue, style: TextStyle( fontSize: 18, diff --git a/lib/src/widgets/alert_with_one_action.dart b/lib/src/widgets/alert_with_one_action.dart index 12ba84d17a..6f0ba5e8bc 100644 --- a/lib/src/widgets/alert_with_one_action.dart +++ b/lib/src/widgets/alert_with_one_action.dart @@ -11,6 +11,7 @@ class AlertWithOneAction extends BaseAlertDialog { this.headerTitleText, this.headerImageProfileUrl, this.buttonKey, + Key? key, }); final String alertTitle; @@ -65,4 +66,4 @@ class AlertWithOneAction extends BaseAlertDialog { ), ); } -} \ No newline at end of file +} diff --git a/lib/src/widgets/base_alert_dialog.dart b/lib/src/widgets/base_alert_dialog.dart index 5c11117403..0f1ca988c8 100644 --- a/lib/src/widgets/base_alert_dialog.dart +++ b/lib/src/widgets/base_alert_dialog.dart @@ -33,6 +33,10 @@ class BaseAlertDialog extends StatelessWidget { String? get headerImageUrl => null; + Key? leftActionButtonKey; + + Key? rightActionButtonKey; + Widget title(BuildContext context) { return Text( titleText, @@ -87,6 +91,7 @@ class BaseAlertDialog extends StatelessWidget { children: [ Expanded( child: TextButton( + key: leftActionButtonKey, onPressed: actionLeft, style: TextButton.styleFrom( backgroundColor: @@ -109,6 +114,7 @@ class BaseAlertDialog extends StatelessWidget { const VerticalSectionDivider(), Expanded( child: TextButton( + key: rightActionButtonKey, onPressed: actionRight, style: TextButton.styleFrom( backgroundColor: From 97fced4cad33feddfa6b4f88429852ba4924460c Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Mon, 1 Jul 2024 13:00:17 +0100 Subject: [PATCH 07/69] test: Complete Exchange flow --- integration_test/app_test.dart | 5 +- .../components/common_checks.dart | 5 + .../robots/exchange_page_robot.dart | 8 +- .../robots/exchange_trade_page_robot.dart | 138 ++++++++++++++---- 4 files changed, 121 insertions(+), 35 deletions(-) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index d7e4393fa5..00d1516057 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -59,6 +59,7 @@ void main() { final pin = [0, 8, 0, 1]; + // String testAmount = '0.08'; String testAmount = '8'; CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; @@ -146,9 +147,9 @@ void main() { await exchangeTradePageRobot.onConfirmSendingButtonPressed(); - // await exchangeTradePageRobot.handleSendSuccessOrFailure(); + await exchangeTradePageRobot.handleSendSuccessOrFailure(); - // await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed(); + await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed(); }); }); } diff --git a/integration_test/components/common_checks.dart b/integration_test/components/common_checks.dart index a5335fe8ee..eebad62e4a 100644 --- a/integration_test/components/common_checks.dart +++ b/integration_test/components/common_checks.dart @@ -16,6 +16,11 @@ class CommonTestCases { await tester.pumpAndSettle(); } + Future tapItemByFinder(Finder finder) async { + await tester.tap(finder); + await tester.pumpAndSettle(); + } + void hasText(String text, {bool hasWidget = true}) { final textWidget = find.text(text); expect(textWidget, hasWidget ? findsOneWidget : findsNothing); diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart index 895845c077..a0d9bd337f 100644 --- a/integration_test/robots/exchange_page_robot.dart +++ b/integration_test/robots/exchange_page_robot.dart @@ -252,9 +252,9 @@ class ExchangePageRobot { isMinLimitError = hasMinLimitError(); - int amount; + double amount; - amount = int.parse(initialAmount); + amount = double.parse(initialAmount); int maxRetries = 20; int retries = 0; @@ -289,9 +289,9 @@ class ExchangePageRobot { isMaxLimitError = hasMaxLimitError(); - int amount; + double amount; - amount = int.parse(initialAmount); + amount = double.parse(initialAmount); int maxRetries = 20; int retries = 0; diff --git a/integration_test/robots/exchange_trade_page_robot.dart b/integration_test/robots/exchange_trade_page_robot.dart index 49848f1a56..30ddb474e1 100644 --- a/integration_test/robots/exchange_trade_page_robot.dart +++ b/integration_test/robots/exchange_trade_page_robot.dart @@ -1,3 +1,7 @@ +import 'dart:async'; + +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -24,48 +28,124 @@ class ExchangeTradePageRobot { } Future onConfirmSendingButtonPressed() async { - await commonTestCases.tapItemByKey('exchange_trade_page_confirm_sending_button_key'); + tester.printToConsole('Now confirming sending'); + + final widget = find.byKey(ValueKey('exchange_trade_page_confirm_sending_button_key')); + await tester.tap(widget); + await tester.pump(); + + final Completer completer = Completer(); + + // Loop to wait for the async operation to complete + while (true) { + await Future.delayed(Duration(seconds: 1)); + + final ExchangeTradeState state = tester.state(find.byType(ExchangeTradeForm)); + final execState = state.widget.exchangeTradeViewModel.sendViewModel.state; + + bool isDone = execState is ExecutedSuccessfullyState; + bool isFailed = execState is FailureState; + + tester.printToConsole('isDone: $isDone'); + tester.printToConsole('isFailed: $isFailed'); + + if (isDone || isFailed) { + tester.printToConsole( + isDone ? 'Completer is done' : 'Completer is done though operation failed'); + completer.complete(); + await tester.pump(); + break; + } else { + tester.printToConsole('Completer is not done'); + await tester.pump(); + } + } + + await expectLater(completer.future, completes); + + tester.printToConsole('Done confirming sending'); + + await commonTestCases.defaultSleepTime(seconds: 4); } Future onSendButtonOnConfirmSendingDialogPressed() async { - await commonTestCases - .tapItemByKey('exchange_trade_page_confirm_sending_dialog_send_button_key'); - await commonTestCases.defaultSleepTime(); + tester.printToConsole('Send Button on Confirm Dialog Triggered'); + await commonTestCases.defaultSleepTime(seconds: 4); + + final sendText = find.text(S.current.send); + bool hasText = sendText.tryEvaluate(); + + if (hasText) { + await commonTestCases.tapItemByFinder(sendText); + + await commonTestCases.defaultSleepTime(seconds: 4); + } } Future onCancelButtonOnConfirmSendingDialogPressed() async { - await commonTestCases - .tapItemByKey('exchange_trade_page_confirm_sending_dialog_cancel_button_key'); + tester.printToConsole('Cancel Button on Confirm Dialog Triggered'); + + await commonTestCases.tapItemByKey( + 'exchange_trade_page_confirm_sending_dialog_cancel_button_key', + ); + await commonTestCases.defaultSleepTime(); } - bool hasErrorWhileSending() { - final errorDialog = find.byKey(ValueKey('exchange_trade_page_send_failure_dialog_button_key')); + Future onSendFailureDialogButtonPressed() async { + await commonTestCases.defaultSleepTime(seconds: 6); + + tester.printToConsole('Send Button Failure Dialog Triggered'); + + await commonTestCases.tapItemByKey('exchange_trade_page_send_failure_dialog_button_key'); + } + + Future hasErrorWhileSending() async { + await tester.pump(); + + tester.printToConsole('Checking if there is an error'); + + final errorDialog = find.byKey( + ValueKey('exchange_trade_page_send_failure_dialog_button_key'), + ); + bool hasError = errorDialog.tryEvaluate(); + + tester.printToConsole('Has error: $hasError'); + return hasError; } - Future onSendFailureDialogButtonPressed() async { - await commonTestCases.tapItemByKey('exchange_trade_page_send_failure_dialog_button_key'); + Future handleSendSuccessOrFailure() async { + bool hasError = false; + + hasError = await hasErrorWhileSending(); + + int maxRetries = 20; + int retries = 0; + + while (hasError && retries < maxRetries) { + tester.printToConsole('hasErrorInLoop: $hasError'); + await tester.pump(); + + await onSendFailureDialogButtonPressed(); + tester.printToConsole('Failure button tapped'); + + await commonTestCases.defaultSleepTime(); + + await onConfirmSendingButtonPressed(); + tester.printToConsole('Confirm sending button tapped'); + + hasError = await hasErrorWhileSending(); + + retries++; + } + + if (!hasError) { + tester.printToConsole('No error, proceeding with flow'); + await tester.pump(); + } + await commonTestCases.defaultSleepTime(); } - - // Future handleSendSuccessOrFailure() async { - // bool hasError = false; - - // hasError = hasErrorWhileSending(); - - // if (hasError) { - // tester.printToConsole('hasError: $hasError'); - // await onSendFailureDialogButtonPressed(); - // tester.printToConsole('Failure button tapped'); - // await onConfirmSendingButtonPressed(); - // tester.printToConsole('Confirm sending tapped'); - // await handleSendSuccessOrFailure(); - // tester.printToConsole('Let\'s go'); - // } else { - // await onSendButtonOnConfirmSendingDialogPressed(); - // return; - // } - // } } From f27deb2f19f1f1e6cb25d2fe5541a65b76894252 Mon Sep 17 00:00:00 2001 From: OmarHatem Date: Wed, 3 Jul 2024 03:15:58 +0300 Subject: [PATCH 08/69] fix dependency issue --- cw_bitcoin/pubspec.yaml | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 62afe3df00..b0c1aa1794 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -43,10 +43,9 @@ dependencies: git: url: https://github.com/cake-tech/ledger-bitcoin sp_scanner: - path: ../../sp_scanner - # git: - # url: https://github.com/cake-tech/sp_scanner - # ref: sp_v2.0.0 + git: + url: https://github.com/cake-tech/sp_scanner + ref: sp_v2.1.0 dev_dependencies: From 7b99667a14c62d970a97cf1a5aad33094f40c745 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 11:28:02 +0100 Subject: [PATCH 09/69] test: Final cleanups --- integration_test/app_test.dart | 18 ++++-------------- .../robots/pin_code_widget_robot.dart | 4 ---- 2 files changed, 4 insertions(+), 18 deletions(-) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index 00d1516057..4b28e98219 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -1,7 +1,6 @@ import 'package:cake_wallet/main.dart' as app; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; @@ -18,15 +17,6 @@ import 'robots/setup_pin_code_robot.dart'; import 'robots/welcome_page_robot.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; -Future restoreFlutterError() async { - final originalOnError = FlutterError.onError!; - - // restore FlutterError.onError - FlutterError.onError = (FlutterErrorDetails details) { - originalOnError(details); - }; -} - void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -59,10 +49,10 @@ void main() { final pin = [0, 8, 0, 1]; - // String testAmount = '0.08'; - String testAmount = '8'; - CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; - CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; + String testAmount = '0.08'; + // String testAmount = '8'; + CryptoCurrency testReceiveCurrency = CryptoCurrency.usdtSol; + CryptoCurrency testDepositCurrency = CryptoCurrency.sol; WalletType testWalletType = WalletType.solana; String testWalletName = 'Integrated Testing Wallet'; diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart index 7fa0bcd9cc..3b1df0cc77 100644 --- a/integration_test/robots/pin_code_widget_robot.dart +++ b/integration_test/robots/pin_code_widget_robot.dart @@ -29,15 +29,11 @@ class PinCodeWidgetRobot { } Future enterPinCode(List pinCode, bool isFirstEntry) async { - // final PinCodeWidget pinCodeState = tester.widget(find.bySubtype()); for (int pin in pinCode) { await pushPinButton(pin); } - // the state is cleared once it get's to the last entry - // expect(pinCodeState. .pin, isFirstEntry ? '' : '0801'); - await commonTestCases.defaultSleepTime(); } } From e7670c2390bfdffdca4703e63f8368033f295a26 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 14:26:40 +0100 Subject: [PATCH 10/69] feat: Add CI to run automated integration tests withan android emulator --- .../workflows/automated_integration_tests.yml | 66 +++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 .github/workflows/automated_integration_tests.yml diff --git a/.github/workflows/automated_integration_tests.yml b/.github/workflows/automated_integration_tests.yml new file mode 100644 index 0000000000..112260a9cb --- /dev/null +++ b/.github/workflows/automated_integration_tests.yml @@ -0,0 +1,66 @@ +name: Automated Integration Tests + +on: + pull_request: + branches: + [main] + +jobs: + Automated_integration_tests: + runs-on: ubuntu-latest + strategy: + matrix: + api-level: [23, 24, 29] + + steps: + - name: 🤌🏼 Checkout + uses: actions/checkout@v4 + + - name: 🦾 Use JDK 11 + uses: actions/setup-java@v1 + with: + java-version: "11.x" + + - name: 🦾 Use Flutter 3.19.6 + uses: subosito/flutter-action@v1 + with: + flutter-version: "3.19.6" + channel: stable + + - name: 🦾 Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: 🦾 Cache gradle + uses: gradle/actions/setup-gradle@v3 + + - name: 🦾 Cache AVD + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: 🦾 Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: 🚀 Integration tests on Android Emualator + timeout-minutes: 30 + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: flutter test integration_test \ No newline at end of file From b915e274a7da34348ca8b5300981d923938fd127 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 14:46:20 +0100 Subject: [PATCH 11/69] feat: Adjust Automated integration test CI to run on ubuntu 20.04-a --- .github/workflows/automated_integration_tests.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/automated_integration_tests.yml b/.github/workflows/automated_integration_tests.yml index 112260a9cb..8c195be2f1 100644 --- a/.github/workflows/automated_integration_tests.yml +++ b/.github/workflows/automated_integration_tests.yml @@ -2,12 +2,11 @@ name: Automated Integration Tests on: pull_request: - branches: - [main] + branches: [ main ] jobs: Automated_integration_tests: - runs-on: ubuntu-latest + runs-on: ubuntu-20.04 strategy: matrix: api-level: [23, 24, 29] From 6e4c9e8767e9a319da0d61310e9626ee54fcbaf5 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 16:51:46 +0100 Subject: [PATCH 12/69] fix: Move integration test CI into PR test build CI --- .../workflows/automated_integration_tests.yml | 65 ------------------- .github/workflows/pr_test_build.yml | 35 ++++++++++ 2 files changed, 35 insertions(+), 65 deletions(-) delete mode 100644 .github/workflows/automated_integration_tests.yml diff --git a/.github/workflows/automated_integration_tests.yml b/.github/workflows/automated_integration_tests.yml deleted file mode 100644 index 8c195be2f1..0000000000 --- a/.github/workflows/automated_integration_tests.yml +++ /dev/null @@ -1,65 +0,0 @@ -name: Automated Integration Tests - -on: - pull_request: - branches: [ main ] - -jobs: - Automated_integration_tests: - runs-on: ubuntu-20.04 - strategy: - matrix: - api-level: [23, 24, 29] - - steps: - - name: 🤌🏼 Checkout - uses: actions/checkout@v4 - - - name: 🦾 Use JDK 11 - uses: actions/setup-java@v1 - with: - java-version: "11.x" - - - name: 🦾 Use Flutter 3.19.6 - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.19.6" - channel: stable - - - name: 🦾 Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: 🚀 Integration tests on Android Emualator - timeout-minutes: 30 - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: flutter test integration_test \ No newline at end of file diff --git a/.github/workflows/pr_test_build.yml b/.github/workflows/pr_test_build.yml index 841ea570db..59b5162a26 100644 --- a/.github/workflows/pr_test_build.yml +++ b/.github/workflows/pr_test_build.yml @@ -13,6 +13,9 @@ on: jobs: PR_test_build: runs-on: ubuntu-20.04 + strategy: + matrix: + api-level: [29] env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet @@ -202,3 +205,35 @@ jobs: title: "${{ env.BRANCH_NAME }}.apk" filename: ${{ env.BRANCH_NAME }}.apk initial_comment: ${{ github.event.head_commit.message }} + + - name: 🦾 Cache gradle + uses: gradle/actions/setup-gradle@v3 + + - name: 🦾 Cache AVD + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: 🦾 Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: 🚀 Integration tests on Android Emualator + timeout-minutes: 30 + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: flutter test integration_test From ab420e4567447aeabfef795945cf43047342e1d5 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 17:44:27 +0100 Subject: [PATCH 13/69] ci: Add automated test ci which is a streamlined replica of pr test build ci --- .../workflows/automated_integration_test.yml | 192 ++++++++++++++++++ 1 file changed, 192 insertions(+) create mode 100644 .github/workflows/automated_integration_test.yml diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml new file mode 100644 index 0000000000..1bb8aab6f4 --- /dev/null +++ b/.github/workflows/automated_integration_test.yml @@ -0,0 +1,192 @@ +name: Automated Integration Test + +on: + pull_request: + branches: [main] + workflow_dispatch: + inputs: + branch: + description: "Branch name to build" + required: true + default: "main" + +jobs: + Automated_integration_test: + runs-on: ubuntu-20.04 + strategy: + matrix: + api-level: [29] + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + PR_NUMBER: ${{ github.event.number }} + + steps: + + - name: Free Up GitHub Actions Ubuntu Runner Disk Space + run: | + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + + - uses: actions/checkout@v2 + - uses: actions/setup-java@v1 + with: + java-version: "11.x" + + - name: Flutter action + uses: subosito/flutter-action@v1 + with: + flutter-version: "3.19.6" + channel: stable + + - name: Install package dependencies + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + + - name: Execute Build and Setup Commands + run: | + sudo mkdir -p /opt/android + sudo chown $USER /opt/android + cd /opt/android + -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + cargo install cargo-ndk + git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} + cd cake_wallet/scripts/android/ + ./install_ndk.sh + source ./app_env.sh cakewallet + chmod +x pubspec_gen.sh + ./app_config.sh + + - name: Cache Externals + id: cache-externals + uses: actions/cache@v3 + with: + path: | + /opt/android/cake_wallet/cw_haven/android/.cxx + /opt/android/cake_wallet/cw_haven/ios/External + /opt/android/cake_wallet/cw_monero/android/.cxx + /opt/android/cake_wallet/cw_monero/ios/External + /opt/android/cake_wallet/cw_shared_external/ios/External + key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }} + + - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} + name: Generate Externals + run: | + cd /opt/android/cake_wallet/scripts/android/ + source ./app_env.sh cakewallet + ./build_all.sh + ./copy_monero_deps.sh + + - name: Install Flutter dependencies + run: | + cd /opt/android/cake_wallet + flutter pub get + + - name: Generate KeyStore + run: | + cd /opt/android/cake_wallet/android/app + keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + + - name: Generate key properties + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + + - name: Generate localization + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_localization.dart + + - name: Build generated code + run: | + cd /opt/android/cake_wallet + ./model_generator.sh + + - name: Add secrets + run: | + cd /opt/android/cake_wallet + touch lib/.secrets.g.dart + touch cw_evm/lib/.secrets.g.dart + touch cw_solana/lib/.secrets.g.dart + touch cw_tron/lib/.secrets.g.dart + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart + echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart + echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart + echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart + echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart + echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart + echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart + echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart + echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart + echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart + echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart + echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + + - name: Rename app + run: | + echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties + + - name: 🦾 Cache gradle + uses: gradle/actions/setup-gradle@v3 + + - name: 🦾 Cache AVD + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: 🦾 Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: 🚀 Integration tests on Android Emualator + timeout-minutes: 30 + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: flutter test integration_test From e2ee119684e33917df549f340b722754b9ac04d0 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 17:49:40 +0100 Subject: [PATCH 14/69] ci: Re-add step to access branch name --- .github/workflows/automated_integration_test.yml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 1bb8aab6f4..f08d0b2eac 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -22,6 +22,13 @@ jobs: PR_NUMBER: ${{ github.event.number }} steps: + - name: is pr + if: github.event_name == 'pull_request' + run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV + + - name: is not pr + if: github.event_name != 'pull_request' + run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV - name: Free Up GitHub Actions Ubuntu Runner Disk Space run: | From 3e1705a15b05f7da73a2b8faa09745ddd62878e6 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 18:05:42 +0100 Subject: [PATCH 15/69] ci: Add KVM --- .github/workflows/automated_integration_test.yml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index f08d0b2eac..a9ccd7e1c5 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -166,6 +166,12 @@ jobs: run: | echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties + - name: Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + - name: 🦾 Cache gradle uses: gradle/actions/setup-gradle@v3 From bf5cfbe1b1ec14bbf75c5a059e8aaa8c0e0c4426 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 18:06:54 +0100 Subject: [PATCH 16/69] ci: Add filepath to trigger the test run from --- .github/workflows/automated_integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index a9ccd7e1c5..02081ce4b3 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -202,4 +202,4 @@ jobs: force-avd-creation: false emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: flutter test integration_test + script: cd /opt/android/cake_wallet && flutter test integration_test From bcab3c7c8551a1ef97e518b0524c35eb4b9f859c Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 18:54:16 +0100 Subject: [PATCH 17/69] ci: Add required key --- .github/workflows/automated_integration_test.yml | 1 + tool/utils/secret_key.dart | 1 + 2 files changed, 2 insertions(+) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 02081ce4b3..c7ffb8ddfa 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -118,6 +118,7 @@ jobs: touch cw_solana/lib/.secrets.g.dart touch cw_tron/lib/.secrets.g.dart echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + echo "const seeds = '${{ secrets.SEEDS }}';" > lib/.secrets.g.dart echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 9559e83b3d..f75dbb2c0a 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -39,6 +39,7 @@ class SecretKey { SecretKey('moralisApiKey', () => ''), SecretKey('ankrApiKey', () => ''), SecretKey('quantexExchangeMarkup', () => ''), + SecretKey('seeds', () => ''), ]; static final evmChainsSecrets = [ From 30802cb825ae78c0e00ec092bfa47b697eec11be Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 19:18:13 +0100 Subject: [PATCH 18/69] ci: Add required key --- .github/workflows/automated_integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index c7ffb8ddfa..8218c53553 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -118,9 +118,9 @@ jobs: touch cw_solana/lib/.secrets.g.dart touch cw_tron/lib/.secrets.g.dart echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - echo "const seeds = '${{ secrets.SEEDS }}';" > lib/.secrets.g.dart echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + echo "const seeds = '${{ secrets.SEEDS }}';" > lib/.secrets.g.dart echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart From edd57b10ded4448f0cb57f869ef69d656cf1fc24 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 20:09:17 +0100 Subject: [PATCH 19/69] ci: Add missing secret key --- .github/workflows/automated_integration_test.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 8218c53553..d6c218da59 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -120,7 +120,6 @@ jobs: echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - echo "const seeds = '${{ secrets.SEEDS }}';" > lib/.secrets.g.dart echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart @@ -162,7 +161,8 @@ jobs: echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - + echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + - name: Rename app run: | echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties From 7c6e6a414c9ca60be182afd59d4c6cf831fb4078 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 3 Jul 2024 20:48:50 +0100 Subject: [PATCH 20/69] ci: Add missing secret key --- .github/workflows/automated_integration_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index d6c218da59..7b2fe46398 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -120,6 +120,7 @@ jobs: echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + echo "const seeds = '${{ secrets.SEEDS }}';" >> lib/.secrets.g.dart echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart From fd65b21702155697afbcc5de336c4a0a462ceddd Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 5 Jul 2024 08:25:02 +0100 Subject: [PATCH 21/69] ci: Add nano secrets to workflow --- .github/workflows/automated_integration_test.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 7b2fe46398..d9ead67dd1 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -116,6 +116,8 @@ jobs: touch lib/.secrets.g.dart touch cw_evm/lib/.secrets.g.dart touch cw_solana/lib/.secrets.g.dart + touch cw_core/lib/.secrets.g.dart + touch cw_nano/lib/.secrets.g.dart touch cw_tron/lib/.secrets.g.dart echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart @@ -161,6 +163,7 @@ jobs: echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart From d49a52668848eeb4cdc68598083eedc72493a3e1 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 5 Jul 2024 13:11:50 +0100 Subject: [PATCH 22/69] ci: Switch step to free space on runner --- .github/workflows/automated_integration_test.yml | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index d9ead67dd1..b2b3cdc6c1 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -30,12 +30,16 @@ jobs: if: github.event_name != 'pull_request' run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV - - name: Free Up GitHub Actions Ubuntu Runner Disk Space - run: | - sudo rm -rf /usr/share/dotnet - sudo rm -rf /opt/ghc - sudo rm -rf "/usr/local/share/boost" - sudo rm -rf "$AGENT_TOOLSDIRECTORY" + - name: Free Disk Space (Ubuntu) + uses: insightsengineering/disk-space-reclaimer@v1 + with: + tools-cache: true + android: false + dotnet: true + haskell: true + large-packages: true + swap-storage: true + docker-images: true - uses: actions/checkout@v2 - uses: actions/setup-java@v1 From 0b92ff479a2f3866140ca89ec87b0f56c5ba6476 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 5 Jul 2024 15:38:00 +0100 Subject: [PATCH 23/69] ci: Remove timeout from workflow --- .github/workflows/automated_integration_test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index b2b3cdc6c1..185e2ad410 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -204,7 +204,6 @@ jobs: script: echo "Generated AVD snapshot for caching." - name: 🚀 Integration tests on Android Emualator - timeout-minutes: 30 uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} From eb56236760612533c631e26015cb7ec17962d5bc Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 5 Jul 2024 15:52:15 +0100 Subject: [PATCH 24/69] ci: Confirm impact that removing copy_monero_deps would have on entire workflow time --- .github/workflows/automated_integration_test.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 185e2ad410..20d89b971c 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -87,7 +87,6 @@ jobs: cd /opt/android/cake_wallet/scripts/android/ source ./app_env.sh cakewallet ./build_all.sh - ./copy_monero_deps.sh - name: Install Flutter dependencies run: | From 3a687eea228a0961d441af7c9eac7737d06cdd46 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 06:52:56 +0100 Subject: [PATCH 25/69] ci: Update CI and temporarily remove cache related to emulator --- .../workflows/automated_integration_test.yml | 65 ++++++++++--------- 1 file changed, 36 insertions(+), 29 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 20d89b971c..dd212f5680 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -12,10 +12,12 @@ on: jobs: Automated_integration_test: - runs-on: ubuntu-20.04 + runs-on: ubuntu-22.04 strategy: + fail-fast: false matrix: api-level: [29] + arch: [x86_64] env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet @@ -46,6 +48,13 @@ jobs: with: java-version: "11.x" + - name: Setup JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: gradle + - name: Flutter action uses: subosito/flutter-action@v1 with: @@ -75,18 +84,15 @@ jobs: with: path: | /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/cw_haven/ios/External - /opt/android/cake_wallet/cw_monero/android/.cxx - /opt/android/cake_wallet/cw_monero/ios/External - /opt/android/cake_wallet/cw_shared_external/ios/External - key: ${{ hashFiles('**/build_monero.sh', '**/build_haven.sh', '**/monero_api.cpp') }} + /opt/android/cake_wallet/scripts/monero_c/release + key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }} - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} name: Generate Externals run: | cd /opt/android/cake_wallet/scripts/android/ source ./app_env.sh cakewallet - ./build_all.sh + ./build_monero_all.sh - name: Install Flutter dependencies run: | @@ -169,7 +175,7 @@ jobs: echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - + - name: Rename app run: | echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties @@ -179,34 +185,35 @@ jobs: echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." + # - name: 🦾 Cache gradle + # uses: gradle/actions/setup-gradle@v3 + + # - name: 🦾 Cache AVD + # uses: actions/cache@v4 + # id: avd-cache + # with: + # path: | + # ~/.android/avd/* + # ~/.android/adb* + # key: avd-${{ matrix.api-level }} + + # - name: 🦾 Create AVD and generate snapshot for caching + # if: steps.avd-cache.outputs.cache-hit != 'true' + # uses: reactivecircus/android-emulator-runner@v2 + # with: + # api-level: ${{ matrix.api-level }} + # force-avd-creation: false + # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + # disable-animations: false + # script: echo "Generated AVD snapshot for caching." - name: 🚀 Integration tests on Android Emualator uses: reactivecircus/android-emulator-runner@v2 with: api-level: ${{ matrix.api-level }} force-avd-creation: false + arch: ${{ matrix.arch }} emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true script: cd /opt/android/cake_wallet && flutter test integration_test From eb228ed5109ba1019f3247425a28d886333d67ec Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 06:54:45 +0100 Subject: [PATCH 26/69] ci: Remove dynamic java version --- .github/workflows/automated_integration_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index dd212f5680..c54d70ac85 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -44,9 +44,9 @@ jobs: docker-images: true - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: "11.x" + # - uses: actions/setup-java@v1 + # with: + # java-version: "11.x" - name: Setup JDK 17 uses: actions/setup-java@v3 From 1504b55d83d8aa45de33ac03c5560ec4619bd4ba Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 07:12:29 +0100 Subject: [PATCH 27/69] ci: Temporarily switch CI --- .github/workflows/automated_integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index c54d70ac85..2f1aa208fc 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -62,7 +62,7 @@ jobs: channel: stable - name: Install package dependencies - run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python-is-python3 libtool libtinfo5 cmake clang - name: Execute Build and Setup Commands run: | From 034c5758248ae2cdaa4d10bfab2c8099691d1850 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 07:20:06 +0100 Subject: [PATCH 28/69] ci: Switch to 11.x jdk --- .github/workflows/automated_integration_test.yml | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 2f1aa208fc..4c054dc480 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -48,12 +48,10 @@ jobs: # with: # java-version: "11.x" - - name: Setup JDK 17 - uses: actions/setup-java@v3 + - name: Setup JDK + uses: actions/setup-java@v1 with: - java-version: '17' - distribution: 'temurin' - cache: gradle + java-version: "11.x" - name: Flutter action uses: subosito/flutter-action@v1 @@ -62,7 +60,7 @@ jobs: channel: stable - name: Install package dependencies - run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python-is-python3 libtool libtinfo5 cmake clang + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - name: Execute Build and Setup Commands run: | From 59407375bd11d5893462a081aef4a75c769a85f6 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 07:31:41 +0100 Subject: [PATCH 29/69] ci: Temporarily switch CI --- .github/workflows/automated_integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 4c054dc480..b7e1b8cab1 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -60,7 +60,7 @@ jobs: channel: stable - name: Install package dependencies - run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python-is-python3 libtool libtinfo5 cmake clang - name: Execute Build and Setup Commands run: | From a5f4475243e91178efa749d07489751c1a42828a Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 07:46:06 +0100 Subject: [PATCH 30/69] ci: Revert ubuntu version --- .github/workflows/automated_integration_test.yml | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index b7e1b8cab1..2de4a41d68 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -12,7 +12,7 @@ on: jobs: Automated_integration_test: - runs-on: ubuntu-22.04 + runs-on: ubuntu-20.04 strategy: fail-fast: false matrix: @@ -44,9 +44,6 @@ jobs: docker-images: true - uses: actions/checkout@v2 - # - uses: actions/setup-java@v1 - # with: - # java-version: "11.x" - name: Setup JDK uses: actions/setup-java@v1 @@ -60,7 +57,7 @@ jobs: channel: stable - name: Install package dependencies - run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python-is-python3 libtool libtinfo5 cmake clang + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - name: Execute Build and Setup Commands run: | From d7c307fb851d8a1e8adb2494e3aba18c90db24e2 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 08:19:48 +0100 Subject: [PATCH 31/69] ci: Add more api levels --- .github/workflows/automated_integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 2de4a41d68..5090eb0f9f 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - api-level: [29] + api-level: [27, 28, 29, 30, 31, 32, 33] arch: [x86_64] env: STORE_PASS: test@cake_wallet From fee1335b17406709a33a94d3506b7eae76b33758 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 08:22:36 +0100 Subject: [PATCH 32/69] ci: Add more target options --- .github/workflows/automated_integration_test.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 5090eb0f9f..5f36723d87 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -18,6 +18,7 @@ jobs: matrix: api-level: [27, 28, 29, 30, 31, 32, 33] arch: [x86_64] + target: [default, google_apis, playstore] env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet From 3e06eb7b67f4ea1307b31a147476b03b7938235c Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 08:53:47 +0100 Subject: [PATCH 33/69] ci: Settled on stable emulator matrix options --- .../workflows/automated_integration_test.yml | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 5f36723d87..06160e1837 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -16,9 +16,9 @@ jobs: strategy: fail-fast: false matrix: - api-level: [27, 28, 29, 30, 31, 32, 33] + api-level: [29] arch: [x86_64] - target: [default, google_apis, playstore] + target: [google_apis] env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet @@ -182,27 +182,27 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - # - name: 🦾 Cache gradle - # uses: gradle/actions/setup-gradle@v3 - - # - name: 🦾 Cache AVD - # uses: actions/cache@v4 - # id: avd-cache - # with: - # path: | - # ~/.android/avd/* - # ~/.android/adb* - # key: avd-${{ matrix.api-level }} - - # - name: 🦾 Create AVD and generate snapshot for caching - # if: steps.avd-cache.outputs.cache-hit != 'true' - # uses: reactivecircus/android-emulator-runner@v2 - # with: - # api-level: ${{ matrix.api-level }} - # force-avd-creation: false - # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - # disable-animations: false - # script: echo "Generated AVD snapshot for caching." + - name: 🦾 Cache gradle + uses: gradle/actions/setup-gradle@v3 + + - name: 🦾 Cache AVD + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: 🦾 Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." - name: 🚀 Integration tests on Android Emualator uses: reactivecircus/android-emulator-runner@v2 @@ -212,4 +212,4 @@ jobs: arch: ${{ matrix.arch }} emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: cd /opt/android/cake_wallet && flutter test integration_test + script: cd /opt/android/cake_wallet && flutter pub get && flutter test integration_test From 27ba3f1a7fbec02345b03d7b5b1381b4cdd20a63 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 09:22:50 +0100 Subject: [PATCH 34/69] ci: Add more target options --- .github/workflows/automated_integration_test.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 06160e1837..a29312f67d 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -16,9 +16,9 @@ jobs: strategy: fail-fast: false matrix: - api-level: [29] - arch: [x86_64] - target: [google_apis] + api-level: [27, 28, 29, 30, 31, 32, 33] + arch: [x86, x86_64] + target: [default, google_apis, playstore] env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet From a1dfc2436fe4470dff52565d6a06037f28894e91 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 10:01:38 +0100 Subject: [PATCH 35/69] ci: Modify flow --- .github/workflows/automated_integration_test.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index a29312f67d..b3fc56a956 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -200,6 +200,8 @@ jobs: with: api-level: ${{ matrix.api-level }} force-avd-creation: false + arch: ${{ matrix.arch }} + target: ${{ matrix.target}} emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: false script: echo "Generated AVD snapshot for caching." @@ -210,6 +212,7 @@ jobs: api-level: ${{ matrix.api-level }} force-avd-creation: false arch: ${{ matrix.arch }} + target: ${{ matrix.target}} emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: cd /opt/android/cake_wallet && flutter pub get && flutter test integration_test + script: cd /opt/android/cake_wallet && flutter pub get && flutter test integration_test/app_test.dart From 065e0aecb5786a56eb85dfdcb59c6115007c1881 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 9 Jul 2024 11:12:29 +0100 Subject: [PATCH 36/69] ci: Streamline api levels to 28 and 29 --- .github/workflows/automated_integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index b3fc56a956..f1a3a00d45 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - api-level: [27, 28, 29, 30, 31, 32, 33] + api-level: [28, 29] arch: [x86, x86_64] target: [default, google_apis, playstore] env: From a4b30ec73793de1f38878d057ccb36ed35e4a8b9 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 11 Jul 2024 10:46:03 +0100 Subject: [PATCH 37/69] ci: One more trial --- .../workflows/automated_integration_test.yml | 46 +++++++++---------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index f1a3a00d45..8ee2d4da41 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -182,29 +182,29 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - arch: ${{ matrix.arch }} - target: ${{ matrix.target}} - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." + # - name: 🦾 Cache gradle + # uses: gradle/actions/setup-gradle@v3 + + # - name: 🦾 Cache AVD + # uses: actions/cache@v4 + # id: avd-cache + # with: + # path: | + # ~/.android/avd/* + # ~/.android/adb* + # key: avd-${{ matrix.api-level }} + + # - name: 🦾 Create AVD and generate snapshot for caching + # if: steps.avd-cache.outputs.cache-hit != 'true' + # uses: reactivecircus/android-emulator-runner@v2 + # with: + # api-level: ${{ matrix.api-level }} + # force-avd-creation: false + # arch: ${{ matrix.arch }} + # target: ${{ matrix.target}} + # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + # disable-animations: false + # script: echo "Generated AVD snapshot for caching." - name: 🚀 Integration tests on Android Emualator uses: reactivecircus/android-emulator-runner@v2 From 83d6efcf5339df8094bb4df7fb59b30ce6a4d692 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 11 Jul 2024 10:54:54 +0100 Subject: [PATCH 38/69] ci: Switch to flutter drive --- .github/workflows/automated_integration_test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 8ee2d4da41..d076855a60 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -215,4 +215,4 @@ jobs: target: ${{ matrix.target}} emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: cd /opt/android/cake_wallet && flutter pub get && flutter test integration_test/app_test.dart + script: cd /opt/android/cake_wallet && flutter pub get && flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart \ No newline at end of file From b41113412c7654f510f6e766dd661f1a90c01162 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 11 Jul 2024 16:07:03 +0100 Subject: [PATCH 39/69] ci: Reduce options --- .../workflows/automated_integration_test.yml | 49 +++++++++---------- 1 file changed, 23 insertions(+), 26 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index d076855a60..01582745e6 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -16,9 +16,8 @@ jobs: strategy: fail-fast: false matrix: - api-level: [28, 29] + api-level: [29] arch: [x86, x86_64] - target: [default, google_apis, playstore] env: STORE_PASS: test@cake_wallet KEY_PASS: test@cake_wallet @@ -182,29 +181,28 @@ jobs: sudo udevadm control --reload-rules sudo udevadm trigger --name-match=kvm - # - name: 🦾 Cache gradle - # uses: gradle/actions/setup-gradle@v3 - - # - name: 🦾 Cache AVD - # uses: actions/cache@v4 - # id: avd-cache - # with: - # path: | - # ~/.android/avd/* - # ~/.android/adb* - # key: avd-${{ matrix.api-level }} - - # - name: 🦾 Create AVD and generate snapshot for caching - # if: steps.avd-cache.outputs.cache-hit != 'true' - # uses: reactivecircus/android-emulator-runner@v2 - # with: - # api-level: ${{ matrix.api-level }} - # force-avd-creation: false - # arch: ${{ matrix.arch }} - # target: ${{ matrix.target}} - # emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - # disable-animations: false - # script: echo "Generated AVD snapshot for caching." + - name: 🦾 Cache gradle + uses: gradle/actions/setup-gradle@v3 + + - name: 🦾 Cache AVD + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: 🦾 Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." - name: 🚀 Integration tests on Android Emualator uses: reactivecircus/android-emulator-runner@v2 @@ -212,7 +210,6 @@ jobs: api-level: ${{ matrix.api-level }} force-avd-creation: false arch: ${{ matrix.arch }} - target: ${{ matrix.target}} emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true script: cd /opt/android/cake_wallet && flutter pub get && flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart \ No newline at end of file From 8ec2097886a201630837aae0ff09592e0ad9505b Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 11 Jul 2024 16:39:53 +0100 Subject: [PATCH 40/69] ci: Remove haven from test --- integration_test/app_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index 4b28e98219..b391873115 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -85,7 +85,7 @@ void main() { // ----------- NewWalletType Page ------------- // Confirm scroll behaviour works properly - await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.haven); + await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.wownero); // Select a wallet and route to next page await newWalletTypePageRobot.selectWalletType(testWalletType); From 89c16519eb3cf099de3f9b245b70425c123f22e9 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 11 Jul 2024 16:43:42 +0100 Subject: [PATCH 41/69] ci: Check for solana in list --- integration_test/app_test.dart | 2 +- ios/Podfile.lock | 38 ------------------- .../flutter/generated_plugin_registrant.cc | 3 ++ windows/flutter/generated_plugins.cmake | 1 + 4 files changed, 5 insertions(+), 39 deletions(-) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index b391873115..b0ccd8a2c7 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -85,7 +85,7 @@ void main() { // ----------- NewWalletType Page ------------- // Confirm scroll behaviour works properly - await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.wownero); + await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.solana); // Select a wallet and route to next page await newWalletTypePageRobot.selectWalletType(testWalletType); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 61aa8b6f22..bafbb07634 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -8,36 +8,6 @@ PODS: - Flutter - ReachabilitySwift - CryptoSwift (1.8.2) - - cw_haven (0.0.1): - - cw_haven/Boost (= 0.0.1) - - cw_haven/Haven (= 0.0.1) - - cw_haven/OpenSSL (= 0.0.1) - - cw_haven/Sodium (= 0.0.1) - - cw_shared_external - - Flutter - - cw_haven/Boost (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/Haven (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/OpenSSL (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/Sodium (0.0.1): - - cw_shared_external - - Flutter - - cw_shared_external (0.0.1): - - cw_shared_external/Boost (= 0.0.1) - - cw_shared_external/OpenSSL (= 0.0.1) - - cw_shared_external/Sodium (= 0.0.1) - - Flutter - - cw_shared_external/Boost (0.0.1): - - Flutter - - cw_shared_external/OpenSSL (0.0.1): - - Flutter - - cw_shared_external/Sodium (0.0.1): - - Flutter - device_display_brightness (0.0.1): - Flutter - device_info_plus (0.0.1): @@ -147,8 +117,6 @@ DEPENDENCIES: - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - CryptoSwift - - cw_haven (from `.symlinks/plugins/cw_haven/ios`) - - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) @@ -197,10 +165,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/barcode_scan2/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" - cw_haven: - :path: ".symlinks/plugins/cw_haven/ios" - cw_shared_external: - :path: ".symlinks/plugins/cw_shared_external/ios" device_display_brightness: :path: ".symlinks/plugins/device_display_brightness/ios" device_info_plus: @@ -257,8 +221,6 @@ SPEC CHECKSUMS: BigInt: f668a80089607f521586bbe29513d708491ef2f7 connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea - cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a - cw_shared_external: 2972d872b8917603478117c9957dfca611845a92 device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 devicelocale: b22617f40038496deffba44747101255cee005b0 diff --git a/windows/flutter/generated_plugin_registrant.cc b/windows/flutter/generated_plugin_registrant.cc index 323f53c9f3..c6444e09cc 100644 --- a/windows/flutter/generated_plugin_registrant.cc +++ b/windows/flutter/generated_plugin_registrant.cc @@ -10,6 +10,7 @@ #include #include #include +#include #include void RegisterPlugins(flutter::PluginRegistry* registry) { @@ -21,6 +22,8 @@ void RegisterPlugins(flutter::PluginRegistry* registry) { registry->GetRegistrarForPlugin("FlutterSecureStorageWindowsPlugin")); PermissionHandlerWindowsPluginRegisterWithRegistrar( registry->GetRegistrarForPlugin("PermissionHandlerWindowsPlugin")); + SharePlusWindowsPluginCApiRegisterWithRegistrar( + registry->GetRegistrarForPlugin("SharePlusWindowsPluginCApi")); UrlLauncherWindowsRegisterWithRegistrar( registry->GetRegistrarForPlugin("UrlLauncherWindows")); } diff --git a/windows/flutter/generated_plugins.cmake b/windows/flutter/generated_plugins.cmake index d6d9b0a49a..0a0b2f9eb6 100644 --- a/windows/flutter/generated_plugins.cmake +++ b/windows/flutter/generated_plugins.cmake @@ -7,6 +7,7 @@ list(APPEND FLUTTER_PLUGIN_LIST flutter_local_authentication flutter_secure_storage_windows permission_handler_windows + share_plus url_launcher_windows ) From 35eb07b780813295896d455f255b5c90a06f4363 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 12 Jul 2024 06:53:13 +0100 Subject: [PATCH 42/69] ci: Adjust amounts and currencies for exchange flow --- integration_test/app_test.dart | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index b0ccd8a2c7..fb1cf7c409 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -49,10 +49,10 @@ void main() { final pin = [0, 8, 0, 1]; - String testAmount = '0.08'; - // String testAmount = '8'; - CryptoCurrency testReceiveCurrency = CryptoCurrency.usdtSol; - CryptoCurrency testDepositCurrency = CryptoCurrency.sol; + // String testAmount = '0.08'; + String testAmount = '8'; + CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; + CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; WalletType testWalletType = WalletType.solana; String testWalletName = 'Integrated Testing Wallet'; From 01cd3b02408d4d23ca45a65c851e502dfdd79bde Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 12 Jul 2024 06:55:41 +0100 Subject: [PATCH 43/69] ci: Set write response on failure to true --- test_driver/integration_test.dart | 1 + 1 file changed, 1 insertion(+) diff --git a/test_driver/integration_test.dart b/test_driver/integration_test.dart index c759dc604d..2e67d866fd 100644 --- a/test_driver/integration_test.dart +++ b/test_driver/integration_test.dart @@ -20,6 +20,7 @@ Future main() async { final resultString = _encodeJson(data); await file.writeAsString(resultString); }, + writeResponseOnFailure: true, ); } From 9dc96d7d22f512fd450f59c22e209cbe793895e6 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 12 Jul 2024 13:55:12 +0100 Subject: [PATCH 44/69] ci: Split ci to funds and non funds related tests --- .../workflows/automated_integration_test.yml | 9 +- ...nds_related_automated_integration_test.yml | 207 ++++++++++++++++++ integration_test/app_test.dart | 9 +- integration_test/funds_related_tests.dart | 145 ++++++++++++ 4 files changed, 362 insertions(+), 8 deletions(-) create mode 100644 .github/workflows/funds_related_automated_integration_test.yml create mode 100644 integration_test/funds_related_tests.dart diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 01582745e6..e2d0fb89c8 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -212,4 +212,11 @@ jobs: arch: ${{ matrix.arch }} emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none disable-animations: true - script: cd /opt/android/cake_wallet && flutter pub get && flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart \ No newline at end of file + script: | + cd /opt/android/cake_wallet + flutter pub get + flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart + if [ $? -ne 0 ]; then + echo "Tests failed" + exit 1 + fi \ No newline at end of file diff --git a/.github/workflows/funds_related_automated_integration_test.yml b/.github/workflows/funds_related_automated_integration_test.yml new file mode 100644 index 0000000000..1d8752c457 --- /dev/null +++ b/.github/workflows/funds_related_automated_integration_test.yml @@ -0,0 +1,207 @@ +name: Funds Related Automated Integration Test + +on: + workflow_dispatch: + inputs: + branch: + description: "Branch name to build" + required: true + default: "main" + +jobs: + Funds_related_automated_integration_test: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + api-level: [29] + arch: [x86, x86_64] + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + + steps: + - name: Free Disk Space (Ubuntu) + uses: insightsengineering/disk-space-reclaimer@v1 + with: + tools-cache: true + android: false + dotnet: true + haskell: true + large-packages: true + swap-storage: true + docker-images: true + + - uses: actions/checkout@v2 + + - name: Setup JDK + uses: actions/setup-java@v1 + with: + java-version: "11.x" + + - name: Flutter action + uses: subosito/flutter-action@v1 + with: + flutter-version: "3.19.6" + channel: stable + + - name: Install package dependencies + run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + + - name: Execute Build and Setup Commands + run: | + sudo mkdir -p /opt/android + sudo chown $USER /opt/android + cd /opt/android + -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + cargo install cargo-ndk + git clone https://github.com/cake-tech/cake_wallet.git --branch main + cd cake_wallet/scripts/android/ + ./install_ndk.sh + source ./app_env.sh cakewallet + chmod +x pubspec_gen.sh + ./app_config.sh + + - name: Cache Externals + id: cache-externals + uses: actions/cache@v3 + with: + path: | + /opt/android/cake_wallet/cw_haven/android/.cxx + /opt/android/cake_wallet/scripts/monero_c/release + key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }} + + - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} + name: Generate Externals + run: | + cd /opt/android/cake_wallet/scripts/android/ + source ./app_env.sh cakewallet + ./build_monero_all.sh + + - name: Install Flutter dependencies + run: | + cd /opt/android/cake_wallet + flutter pub get + + - name: Generate KeyStore + run: | + cd /opt/android/cake_wallet/android/app + keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + + - name: Generate key properties + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + + - name: Generate localization + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_localization.dart + + - name: Build generated code + run: | + cd /opt/android/cake_wallet + ./model_generator.sh + + - name: Add secrets + run: | + cd /opt/android/cake_wallet + touch lib/.secrets.g.dart + touch cw_evm/lib/.secrets.g.dart + touch cw_solana/lib/.secrets.g.dart + touch cw_core/lib/.secrets.g.dart + touch cw_nano/lib/.secrets.g.dart + touch cw_tron/lib/.secrets.g.dart + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + echo "const seeds = '${{ secrets.SEEDS }}';" >> lib/.secrets.g.dart + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart + echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart + echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart + echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart + echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart + echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart + echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart + echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart + echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart + echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart + echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart + echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + + - name: 🦾 Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: 🦾 Cache gradle + uses: gradle/actions/setup-gradle@v3 + + - name: 🦾 Cache AVD + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: 🦾 Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: 🚀 Integration tests on Android Emualator + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + arch: ${{ matrix.arch }} + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: | + cd /opt/android/cake_wallet + flutter pub get + flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart + if [ $? -ne 0 ]; then + echo "Tests failed" + exit 1 + fi \ No newline at end of file diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart index fb1cf7c409..0d8bdb386b 100644 --- a/integration_test/app_test.dart +++ b/integration_test/app_test.dart @@ -33,7 +33,8 @@ void main() { ExchangeTradePageRobot exchangeTradePageRobot; group('Startup Test', () { - testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL', + testWidgets( + 'Test for Exchange flow using Restore Wallet, -Up to the point where the sending is to be triggered', (tester) async { authPageRobot = AuthPageRobot(tester); welcomePageRobot = WelcomePageRobot(tester); @@ -134,12 +135,6 @@ void main() { await exchangeTradePageRobot.isExchangeTradePage(); exchangeTradePageRobot.hasInformationDialog(); await exchangeTradePageRobot.onGotItButtonPressed(); - - await exchangeTradePageRobot.onConfirmSendingButtonPressed(); - - await exchangeTradePageRobot.handleSendSuccessOrFailure(); - - await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed(); }); }); } diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart new file mode 100644 index 0000000000..fb1cf7c409 --- /dev/null +++ b/integration_test/funds_related_tests.dart @@ -0,0 +1,145 @@ +import 'package:cake_wallet/main.dart' as app; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import 'robots/auth_page_robot.dart'; +import 'robots/dashboard_page_robot.dart'; +import 'robots/disclaimer_page_robot.dart'; +import 'robots/exchange_confirm_page_robot.dart'; +import 'robots/exchange_page_robot.dart'; +import 'robots/exchange_trade_page_robot.dart'; +import 'robots/new_wallet_type_page_robot.dart'; +import 'robots/restore_from_seed_or_key_robot.dart'; +import 'robots/restore_options_page_robot.dart'; +import 'robots/setup_pin_code_robot.dart'; +import 'robots/welcome_page_robot.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + DisclaimerPageRobot disclaimerPageRobot; + WelcomePageRobot welcomePageRobot; + SetupPinCodeRobot setupPinCodeRobot; + RestoreOptionsPageRobot restoreOptionsPageRobot; + NewWalletTypePageRobot newWalletTypePageRobot; + RestoreFromSeedOrKeysPageRobot restoreFromSeedOrKeysPageRobot; + DashboardPageRobot dashboardPageRobot; + ExchangePageRobot exchangePageRobot; + ExchangeConfirmPageRobot exchangeConfirmPageRobot; + AuthPageRobot authPageRobot; + ExchangeTradePageRobot exchangeTradePageRobot; + + group('Startup Test', () { + testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL', + (tester) async { + authPageRobot = AuthPageRobot(tester); + welcomePageRobot = WelcomePageRobot(tester); + exchangePageRobot = ExchangePageRobot(tester); + setupPinCodeRobot = SetupPinCodeRobot(tester); + dashboardPageRobot = DashboardPageRobot(tester); + disclaimerPageRobot = DisclaimerPageRobot(tester); + exchangeTradePageRobot = ExchangeTradePageRobot(tester); + newWalletTypePageRobot = NewWalletTypePageRobot(tester); + restoreOptionsPageRobot = RestoreOptionsPageRobot(tester); + exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester); + restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester); + + final pin = [0, 8, 0, 1]; + + // String testAmount = '0.08'; + String testAmount = '8'; + CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; + CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; + + WalletType testWalletType = WalletType.solana; + String testWalletName = 'Integrated Testing Wallet'; + String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L'; + + await app.main(); + await tester.pumpAndSettle(); + + // --------- Disclaimer Page ------------ + // Tap checkbox to accept disclaimer + await disclaimerPageRobot.tapDisclaimerCheckbox(); + + // Tap accept button + await disclaimerPageRobot.tapAcceptButton(); + + // --------- Welcome Page --------------- + await welcomePageRobot.navigateToRestoreWalletPage(); + + // ----------- Restore Options Page ----------- + // Route to restore from seeds page to continue flow + await restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); + + // ----------- SetupPinCode Page ------------- + // Confirm initial defaults - Widgets to be displayed etc + await setupPinCodeRobot.isSetupPinCodePage(); + + await setupPinCodeRobot.enterPinCode(pin, true); + await setupPinCodeRobot.enterPinCode(pin, false); + await setupPinCodeRobot.tapSuccessButton(); + + // ----------- NewWalletType Page ------------- + // Confirm scroll behaviour works properly + await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.solana); + + // Select a wallet and route to next page + await newWalletTypePageRobot.selectWalletType(testWalletType); + await newWalletTypePageRobot.onNextButtonPressed(); + + // ----------- RestoreFromSeedOrKeys Page ------------- + await restoreFromSeedOrKeysPageRobot.enterWalletNameText(testWalletName); + await restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.seeds); + await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); + + // ----------- RestoreFromSeedOrKeys Page ------------- + await dashboardPageRobot.navigateToExchangePage(); + + // ----------- Exchange Page ------------- + await exchangePageRobot.isExchangePage(); + exchangePageRobot.hasResetButton(); + await exchangePageRobot.displayBothExchangeCards(); + exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards(); + exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards(); + + await exchangePageRobot.selectDepositCurrency(testDepositCurrency); + await exchangePageRobot.selectReceiveCurrency(testReceiveCurrency); + + await exchangePageRobot.enterDepositAmount(testAmount); + await exchangePageRobot.enterDepositRefundAddress(depositAddress: testWalletAddress); + + await exchangePageRobot.enterReceiveAddress(testWalletAddress); + + await exchangePageRobot.onExchangeButtonPressed(); + + await exchangePageRobot.handleErrors(testAmount); + + final onAuthPage = authPageRobot.onAuthPage(); + if (onAuthPage) { + await authPageRobot.enterPinCode(pin, false); + } + + // ----------- Exchange Confirm Page ------------- + await exchangeConfirmPageRobot.isExchangeConfirmPage(); + + exchangeConfirmPageRobot.confirmComponentsOfTradeDisplayProperly(); + await exchangeConfirmPageRobot.confirmCopyTradeIdToClipBoardWorksProperly(); + await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); + + // ----------- Exchange Trade Page ------------- + await exchangeTradePageRobot.isExchangeTradePage(); + exchangeTradePageRobot.hasInformationDialog(); + await exchangeTradePageRobot.onGotItButtonPressed(); + + await exchangeTradePageRobot.onConfirmSendingButtonPressed(); + + await exchangeTradePageRobot.handleSendSuccessOrFailure(); + + await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed(); + }); + }); +} From 989ecbb2de00fe7607b6dc104b12331d6696d6f9 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 31 Jul 2024 14:13:23 +0100 Subject: [PATCH 45/69] test: Test for Send flow scenario and minor restructuring for test folders and files --- .../workflows/automated_integration_test.yml | 3 +- cw_bitcoin/pubspec.lock | 12 +- cw_core/pubspec.lock | 4 +- cw_haven/pubspec.lock | 20 +- cw_monero/pubspec.lock | 20 +- cw_nano/pubspec.lock | 4 +- cw_wownero/pubspec.lock | 20 +- integration_test/app_test.dart | 140 ------- ...mon_checks.dart => common_test_cases.dart} | 8 +- .../components/common_test_constants.dart | 13 + .../components/common_test_flows.dart | 101 +++++ integration_test/funds_related_tests.dart | 93 +---- integration_test/robots/auth_page_robot.dart | 2 +- .../robots/dashboard_page_robot.dart | 4 +- .../robots/disclaimer_page_robot.dart | 13 +- .../robots/exchange_confirm_page_robot.dart | 4 +- .../robots/exchange_page_robot.dart | 4 +- .../robots/exchange_trade_page_robot.dart | 11 +- .../robots/new_wallet_type_page_robot.dart | 2 +- .../robots/pin_code_widget_robot.dart | 3 +- .../restore_from_seed_or_key_robot.dart | 22 +- .../robots/restore_options_page_robot.dart | 5 +- integration_test/robots/send_page_robot.dart | 349 ++++++++++++++++++ .../robots/setup_pin_code_robot.dart | 2 +- .../robots/welcome_page_robot.dart | 4 +- .../test_suites/exchange_flow_test.dart | 59 +++ .../test_suites/send_flow_test.dart | 41 ++ lib/main.dart | 9 +- .../exchange/widgets/exchange_card.dart | 2 +- .../wallet_restore_from_keys_form.dart | 3 + .../wallet_restore_from_seed_form.dart | 13 +- lib/src/screens/send/send_page.dart | 45 ++- lib/src/screens/send/widgets/send_card.dart | 9 + lib/src/widgets/address_text_field.dart | 45 +-- lib/src/widgets/alert_with_two_actions.dart | 15 + lib/src/widgets/base_alert_dialog.dart | 3 + lib/src/widgets/picker.dart | 27 +- lib/src/widgets/seed_widget.dart | 19 +- 38 files changed, 787 insertions(+), 366 deletions(-) delete mode 100644 integration_test/app_test.dart rename integration_test/components/{common_checks.dart => common_test_cases.dart} (88%) create mode 100644 integration_test/components/common_test_constants.dart create mode 100644 integration_test/components/common_test_flows.dart create mode 100644 integration_test/robots/send_page_robot.dart create mode 100644 integration_test/test_suites/exchange_flow_test.dart create mode 100644 integration_test/test_suites/send_flow_test.dart diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index e2d0fb89c8..1c8e2f423c 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -1,3 +1,4 @@ +# Commenting out for now, will bring back after I get the CI flow for Automated Testing stable name: Automated Integration Test on: @@ -215,7 +216,7 @@ jobs: script: | cd /opt/android/cake_wallet flutter pub get - flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart + flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites if [ $? -ne 0 ]; then echo "Tests failed" exit 1 diff --git a/cw_bitcoin/pubspec.lock b/cw_bitcoin/pubspec.lock index 15f7cdb437..b3a9cc7937 100644 --- a/cw_bitcoin/pubspec.lock +++ b/cw_bitcoin/pubspec.lock @@ -305,18 +305,18 @@ packages: dependency: transitive description: name: ffigen - sha256: d3e76c2ad48a4e7f93a29a162006f00eba46ce7c08194a77bb5c5e97d1b5ff0a + sha256: "3e12e80ccb6539bb3917217bb6f32709220efb737de0d0fa8736da0b7cb507da" url: "https://pub.dev" source: hosted - version: "8.0.2" + version: "12.0.0" file: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -793,8 +793,8 @@ packages: dependency: "direct main" description: path: "." - ref: "sp_v2.0.0" - resolved-ref: "62c152b9086cd968019128845371072f7e1168de" + ref: "sp_v2.1.0" + resolved-ref: b4b8fbd8d832198d3cee853feb427c4ec482dd7d url: "https://github.com/cake-tech/sp_scanner" source: git version: "0.0.1" diff --git a/cw_core/pubspec.lock b/cw_core/pubspec.lock index 518c71b949..53946925c3 100644 --- a/cw_core/pubspec.lock +++ b/cw_core/pubspec.lock @@ -205,10 +205,10 @@ packages: dependency: "direct main" description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: diff --git a/cw_haven/pubspec.lock b/cw_haven/pubspec.lock index b8583d219d..667eb1744c 100644 --- a/cw_haven/pubspec.lock +++ b/cw_haven/pubspec.lock @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -254,10 +254,10 @@ packages: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: @@ -514,14 +514,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: @@ -707,10 +699,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" yaml: dependency: transitive description: diff --git a/cw_monero/pubspec.lock b/cw_monero/pubspec.lock index 011fed169e..8a522875ff 100644 --- a/cw_monero/pubspec.lock +++ b/cw_monero/pubspec.lock @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -254,10 +254,10 @@ packages: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: @@ -555,14 +555,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: @@ -748,10 +740,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" yaml: dependency: transitive description: diff --git a/cw_nano/pubspec.lock b/cw_nano/pubspec.lock index 70f2f6f0bc..f2c9f3ccf4 100644 --- a/cw_nano/pubspec.lock +++ b/cw_nano/pubspec.lock @@ -252,10 +252,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: diff --git a/cw_wownero/pubspec.lock b/cw_wownero/pubspec.lock index 011fed169e..8a522875ff 100644 --- a/cw_wownero/pubspec.lock +++ b/cw_wownero/pubspec.lock @@ -212,10 +212,10 @@ packages: dependency: transitive description: name: file - sha256: "1b92bec4fc2a72f59a8e15af5f52cd441e4a7860b49499d69dfa817af20e925d" + sha256: "5fc22d7c25582e38ad9a8515372cd9a93834027aacf1801cf01164dac0ffa08c" url: "https://pub.dev" source: hosted - version: "6.1.4" + version: "7.0.0" fixnum: dependency: transitive description: @@ -254,10 +254,10 @@ packages: dependency: transitive description: name: glob - sha256: "4515b5b6ddb505ebdd242a5f2cc5d22d3d6a80013789debfbda7777f47ea308c" + sha256: "0e7014b3b7d4dac1ca4d6114f82bf1782ee86745b9b42a92c9289c23d8a0ab63" url: "https://pub.dev" source: hosted - version: "2.1.1" + version: "2.1.2" graphs: dependency: transitive description: @@ -555,14 +555,6 @@ packages: url: "https://pub.dev" source: hosted version: "1.5.1" - process: - dependency: transitive - description: - name: process - sha256: "53fd8db9cec1d37b0574e12f07520d582019cb6c44abf5479a01505099a34a09" - url: "https://pub.dev" - source: hosted - version: "4.2.4" pub_semver: dependency: transitive description: @@ -748,10 +740,10 @@ packages: dependency: transitive description: name: xdg_directories - sha256: bd512f03919aac5f1313eb8249f223bacf4927031bf60b02601f81f687689e86 + sha256: faea9dee56b520b55a566385b84f2e8de55e7496104adada9962e0bd11bcff1d url: "https://pub.dev" source: hosted - version: "0.2.0+3" + version: "1.0.4" yaml: dependency: transitive description: diff --git a/integration_test/app_test.dart b/integration_test/app_test.dart deleted file mode 100644 index 0d8bdb386b..0000000000 --- a/integration_test/app_test.dart +++ /dev/null @@ -1,140 +0,0 @@ -import 'package:cake_wallet/main.dart' as app; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/wallet_type.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:integration_test/integration_test.dart'; - -import 'robots/auth_page_robot.dart'; -import 'robots/dashboard_page_robot.dart'; -import 'robots/disclaimer_page_robot.dart'; -import 'robots/exchange_confirm_page_robot.dart'; -import 'robots/exchange_page_robot.dart'; -import 'robots/exchange_trade_page_robot.dart'; -import 'robots/new_wallet_type_page_robot.dart'; -import 'robots/restore_from_seed_or_key_robot.dart'; -import 'robots/restore_options_page_robot.dart'; -import 'robots/setup_pin_code_robot.dart'; -import 'robots/welcome_page_robot.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; - -void main() { - IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - - DisclaimerPageRobot disclaimerPageRobot; - WelcomePageRobot welcomePageRobot; - SetupPinCodeRobot setupPinCodeRobot; - RestoreOptionsPageRobot restoreOptionsPageRobot; - NewWalletTypePageRobot newWalletTypePageRobot; - RestoreFromSeedOrKeysPageRobot restoreFromSeedOrKeysPageRobot; - DashboardPageRobot dashboardPageRobot; - ExchangePageRobot exchangePageRobot; - ExchangeConfirmPageRobot exchangeConfirmPageRobot; - AuthPageRobot authPageRobot; - ExchangeTradePageRobot exchangeTradePageRobot; - - group('Startup Test', () { - testWidgets( - 'Test for Exchange flow using Restore Wallet, -Up to the point where the sending is to be triggered', - (tester) async { - authPageRobot = AuthPageRobot(tester); - welcomePageRobot = WelcomePageRobot(tester); - exchangePageRobot = ExchangePageRobot(tester); - setupPinCodeRobot = SetupPinCodeRobot(tester); - dashboardPageRobot = DashboardPageRobot(tester); - disclaimerPageRobot = DisclaimerPageRobot(tester); - exchangeTradePageRobot = ExchangeTradePageRobot(tester); - newWalletTypePageRobot = NewWalletTypePageRobot(tester); - restoreOptionsPageRobot = RestoreOptionsPageRobot(tester); - exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester); - restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester); - - final pin = [0, 8, 0, 1]; - - // String testAmount = '0.08'; - String testAmount = '8'; - CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; - CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; - - WalletType testWalletType = WalletType.solana; - String testWalletName = 'Integrated Testing Wallet'; - String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L'; - - await app.main(); - await tester.pumpAndSettle(); - - // --------- Disclaimer Page ------------ - // Tap checkbox to accept disclaimer - await disclaimerPageRobot.tapDisclaimerCheckbox(); - - // Tap accept button - await disclaimerPageRobot.tapAcceptButton(); - - // --------- Welcome Page --------------- - await welcomePageRobot.navigateToRestoreWalletPage(); - - // ----------- Restore Options Page ----------- - // Route to restore from seeds page to continue flow - await restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); - - // ----------- SetupPinCode Page ------------- - // Confirm initial defaults - Widgets to be displayed etc - await setupPinCodeRobot.isSetupPinCodePage(); - - await setupPinCodeRobot.enterPinCode(pin, true); - await setupPinCodeRobot.enterPinCode(pin, false); - await setupPinCodeRobot.tapSuccessButton(); - - // ----------- NewWalletType Page ------------- - // Confirm scroll behaviour works properly - await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.solana); - - // Select a wallet and route to next page - await newWalletTypePageRobot.selectWalletType(testWalletType); - await newWalletTypePageRobot.onNextButtonPressed(); - - // ----------- RestoreFromSeedOrKeys Page ------------- - await restoreFromSeedOrKeysPageRobot.enterWalletNameText(testWalletName); - await restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.seeds); - await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); - - // ----------- RestoreFromSeedOrKeys Page ------------- - await dashboardPageRobot.navigateToExchangePage(); - - // ----------- Exchange Page ------------- - await exchangePageRobot.isExchangePage(); - exchangePageRobot.hasResetButton(); - await exchangePageRobot.displayBothExchangeCards(); - exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards(); - exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards(); - - await exchangePageRobot.selectDepositCurrency(testDepositCurrency); - await exchangePageRobot.selectReceiveCurrency(testReceiveCurrency); - - await exchangePageRobot.enterDepositAmount(testAmount); - await exchangePageRobot.enterDepositRefundAddress(depositAddress: testWalletAddress); - - await exchangePageRobot.enterReceiveAddress(testWalletAddress); - - await exchangePageRobot.onExchangeButtonPressed(); - - await exchangePageRobot.handleErrors(testAmount); - - final onAuthPage = authPageRobot.onAuthPage(); - if (onAuthPage) { - await authPageRobot.enterPinCode(pin, false); - } - - // ----------- Exchange Confirm Page ------------- - await exchangeConfirmPageRobot.isExchangeConfirmPage(); - - exchangeConfirmPageRobot.confirmComponentsOfTradeDisplayProperly(); - await exchangeConfirmPageRobot.confirmCopyTradeIdToClipBoardWorksProperly(); - await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); - - // ----------- Exchange Trade Page ------------- - await exchangeTradePageRobot.isExchangeTradePage(); - exchangeTradePageRobot.hasInformationDialog(); - await exchangeTradePageRobot.onGotItButtonPressed(); - }); - }); -} diff --git a/integration_test/components/common_checks.dart b/integration_test/components/common_test_cases.dart similarity index 88% rename from integration_test/components/common_checks.dart rename to integration_test/components/common_test_cases.dart index eebad62e4a..2e2991804b 100644 --- a/integration_test/components/common_checks.dart +++ b/integration_test/components/common_test_cases.dart @@ -10,15 +10,15 @@ class CommonTestCases { hasType(); } - Future tapItemByKey(String key) async { + Future tapItemByKey(String key, {bool shouldPumpAndSettle = true}) async { final widget = find.byKey(ValueKey(key)); await tester.tap(widget); - await tester.pumpAndSettle(); + shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump(); } - Future tapItemByFinder(Finder finder) async { + Future tapItemByFinder(Finder finder, {bool shouldPumpAndSettle = true}) async { await tester.tap(finder); - await tester.pumpAndSettle(); + shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump(); } void hasText(String text, {bool hasWidget = true}) { diff --git a/integration_test/components/common_test_constants.dart b/integration_test/components/common_test_constants.dart new file mode 100644 index 0000000000..d8381973e3 --- /dev/null +++ b/integration_test/components/common_test_constants.dart @@ -0,0 +1,13 @@ +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/wallet_type.dart'; + +class CommonTestConstants { + static final pin = [0, 8, 0, 1]; + static final String sendTestAmount = '0.00008'; + static final String exchangeTestAmount = '8'; + static final WalletType testWalletType = WalletType.solana; + static final String testWalletName = 'Integrated Testing Wallet'; + static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; + static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; + static final String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L'; +} diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart new file mode 100644 index 0000000000..a7eb1b17ed --- /dev/null +++ b/integration_test/components/common_test_flows.dart @@ -0,0 +1,101 @@ +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import 'package:cake_wallet/.secrets.g.dart' as secrets; +import 'package:cake_wallet/main.dart' as app; + +import '../robots/disclaimer_page_robot.dart'; +import '../robots/new_wallet_type_page_robot.dart'; +import '../robots/restore_from_seed_or_key_robot.dart'; +import '../robots/restore_options_page_robot.dart'; +import '../robots/setup_pin_code_robot.dart'; +import '../robots/welcome_page_robot.dart'; +import 'common_test_cases.dart'; +import 'common_test_constants.dart'; + +class CommonTestFlows { + CommonTestFlows(this._tester) + : _commonTestCases = CommonTestCases(_tester), + _welcomePageRobot = WelcomePageRobot(_tester), + _setupPinCodeRobot = SetupPinCodeRobot(_tester), + _disclaimerPageRobot = DisclaimerPageRobot(_tester), + _newWalletTypePageRobot = NewWalletTypePageRobot(_tester), + _restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester), + _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester); + + final WidgetTester _tester; + final CommonTestCases _commonTestCases; + + final WelcomePageRobot _welcomePageRobot; + final SetupPinCodeRobot _setupPinCodeRobot; + final DisclaimerPageRobot _disclaimerPageRobot; + final NewWalletTypePageRobot _newWalletTypePageRobot; + final RestoreOptionsPageRobot _restoreOptionsPageRobot; + final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot; + + Future startAppFlow(Key key) async { + await app.main(topLevelKey: ValueKey('send_flow_test_app_key')); + + await _tester.pumpAndSettle(); + + // --------- Disclaimer Page ------------ + // Tap checkbox to accept disclaimer + await _disclaimerPageRobot.tapDisclaimerCheckbox(); + + // Tap accept button + await _disclaimerPageRobot.tapAcceptButton(); + } + + Future restoreWalletThroughSeedsFlow() async { + await _welcomeToRestoreFromSeedsPath(); + await _restoreFromSeeds(); + } + + Future restoreWalletThroughKeysFlow() async { + await _welcomeToRestoreFromSeedsPath(); + await _restoreFromKeys(); + } + + Future _welcomeToRestoreFromSeedsPath() async { + // --------- Welcome Page --------------- + await _welcomePageRobot.navigateToRestoreWalletPage(); + + // ----------- Restore Options Page ----------- + // Route to restore from seeds page to continue flow + await _restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); + + // ----------- SetupPinCode Page ------------- + // Confirm initial defaults - Widgets to be displayed etc + await _setupPinCodeRobot.isSetupPinCodePage(); + + await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, true); + await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, false); + await _setupPinCodeRobot.tapSuccessButton(); + + // ----------- NewWalletType Page ------------- + // Confirm scroll behaviour works properly + await _newWalletTypePageRobot + .findParticularWalletTypeInScrollableList(CommonTestConstants.testWalletType); + + // Select a wallet and route to next page + await _newWalletTypePageRobot.selectWalletType(CommonTestConstants.testWalletType); + await _newWalletTypePageRobot.onNextButtonPressed(); + } + + Future _restoreFromSeeds() async { + // ----------- RestoreFromSeedOrKeys Page ------------- + await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName); + await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.seeds); + await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); + } + + Future _restoreFromKeys() async { + await _commonTestCases.swipePage(); + await _commonTestCases.defaultSleepTime(); + + await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName); + + await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(''); + await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); + } +} diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart index fb1cf7c409..9d97d47f84 100644 --- a/integration_test/funds_related_tests.dart +++ b/integration_test/funds_related_tests.dart @@ -1,100 +1,38 @@ -import 'package:cake_wallet/main.dart' as app; -import 'package:cw_core/crypto_currency.dart'; -import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import 'components/common_test_constants.dart'; +import 'components/common_test_flows.dart'; import 'robots/auth_page_robot.dart'; import 'robots/dashboard_page_robot.dart'; -import 'robots/disclaimer_page_robot.dart'; import 'robots/exchange_confirm_page_robot.dart'; import 'robots/exchange_page_robot.dart'; import 'robots/exchange_trade_page_robot.dart'; -import 'robots/new_wallet_type_page_robot.dart'; -import 'robots/restore_from_seed_or_key_robot.dart'; -import 'robots/restore_options_page_robot.dart'; -import 'robots/setup_pin_code_robot.dart'; -import 'robots/welcome_page_robot.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - DisclaimerPageRobot disclaimerPageRobot; - WelcomePageRobot welcomePageRobot; - SetupPinCodeRobot setupPinCodeRobot; - RestoreOptionsPageRobot restoreOptionsPageRobot; - NewWalletTypePageRobot newWalletTypePageRobot; - RestoreFromSeedOrKeysPageRobot restoreFromSeedOrKeysPageRobot; DashboardPageRobot dashboardPageRobot; ExchangePageRobot exchangePageRobot; ExchangeConfirmPageRobot exchangeConfirmPageRobot; AuthPageRobot authPageRobot; ExchangeTradePageRobot exchangeTradePageRobot; + CommonTestFlows commonTestFlows; group('Startup Test', () { testWidgets('Test for Exchange flow using Restore Wallet - Exchanging USDT(Sol) to SOL', (tester) async { authPageRobot = AuthPageRobot(tester); - welcomePageRobot = WelcomePageRobot(tester); exchangePageRobot = ExchangePageRobot(tester); - setupPinCodeRobot = SetupPinCodeRobot(tester); dashboardPageRobot = DashboardPageRobot(tester); - disclaimerPageRobot = DisclaimerPageRobot(tester); exchangeTradePageRobot = ExchangeTradePageRobot(tester); - newWalletTypePageRobot = NewWalletTypePageRobot(tester); - restoreOptionsPageRobot = RestoreOptionsPageRobot(tester); exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester); - restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(tester); + commonTestFlows = CommonTestFlows(tester); - final pin = [0, 8, 0, 1]; + await commonTestFlows.startAppFlow(ValueKey('funds_exchange_test_app_key')); - // String testAmount = '0.08'; - String testAmount = '8'; - CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; - CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; - - WalletType testWalletType = WalletType.solana; - String testWalletName = 'Integrated Testing Wallet'; - String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L'; - - await app.main(); - await tester.pumpAndSettle(); - - // --------- Disclaimer Page ------------ - // Tap checkbox to accept disclaimer - await disclaimerPageRobot.tapDisclaimerCheckbox(); - - // Tap accept button - await disclaimerPageRobot.tapAcceptButton(); - - // --------- Welcome Page --------------- - await welcomePageRobot.navigateToRestoreWalletPage(); - - // ----------- Restore Options Page ----------- - // Route to restore from seeds page to continue flow - await restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); - - // ----------- SetupPinCode Page ------------- - // Confirm initial defaults - Widgets to be displayed etc - await setupPinCodeRobot.isSetupPinCodePage(); - - await setupPinCodeRobot.enterPinCode(pin, true); - await setupPinCodeRobot.enterPinCode(pin, false); - await setupPinCodeRobot.tapSuccessButton(); - - // ----------- NewWalletType Page ------------- - // Confirm scroll behaviour works properly - await newWalletTypePageRobot.findParticularWalletTypeInScrollableList(WalletType.solana); - - // Select a wallet and route to next page - await newWalletTypePageRobot.selectWalletType(testWalletType); - await newWalletTypePageRobot.onNextButtonPressed(); - - // ----------- RestoreFromSeedOrKeys Page ------------- - await restoreFromSeedOrKeysPageRobot.enterWalletNameText(testWalletName); - await restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.seeds); - await restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); + await commonTestFlows.restoreWalletThroughSeedsFlow(); // ----------- RestoreFromSeedOrKeys Page ------------- await dashboardPageRobot.navigateToExchangePage(); @@ -106,21 +44,22 @@ void main() { exchangePageRobot.confirmRightComponentsDisplayOnDepositExchangeCards(); exchangePageRobot.confirmRightComponentsDisplayOnReceiveExchangeCards(); - await exchangePageRobot.selectDepositCurrency(testDepositCurrency); - await exchangePageRobot.selectReceiveCurrency(testReceiveCurrency); + await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency); + await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency); - await exchangePageRobot.enterDepositAmount(testAmount); - await exchangePageRobot.enterDepositRefundAddress(depositAddress: testWalletAddress); + await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount); + await exchangePageRobot.enterDepositRefundAddress( + depositAddress: CommonTestConstants.testWalletAddress); - await exchangePageRobot.enterReceiveAddress(testWalletAddress); + await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); await exchangePageRobot.onExchangeButtonPressed(); - await exchangePageRobot.handleErrors(testAmount); + await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount); final onAuthPage = authPageRobot.onAuthPage(); if (onAuthPage) { - await authPageRobot.enterPinCode(pin, false); + await authPageRobot.enterPinCode(CommonTestConstants.pin, false); } // ----------- Exchange Confirm Page ------------- @@ -137,7 +76,7 @@ void main() { await exchangeTradePageRobot.onConfirmSendingButtonPressed(); - await exchangeTradePageRobot.handleSendSuccessOrFailure(); + await exchangeTradePageRobot.handleConfirmSendResult(); await exchangeTradePageRobot.onSendButtonOnConfirmSendingDialogPressed(); }); diff --git a/integration_test/robots/auth_page_robot.dart b/integration_test/robots/auth_page_robot.dart index e45a82c0c3..6358d43980 100644 --- a/integration_test/robots/auth_page_robot.dart +++ b/integration_test/robots/auth_page_robot.dart @@ -3,7 +3,7 @@ import 'package:cake_wallet/src/screens/auth/auth_page.dart'; import 'package:flutter/widgets.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; import 'pin_code_widget_robot.dart'; class AuthPageRobot extends PinCodeWidgetRobot { diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart index d8fb4211ae..fc917c3b2d 100644 --- a/integration_test/robots/dashboard_page_robot.dart +++ b/integration_test/robots/dashboard_page_robot.dart @@ -3,7 +3,7 @@ import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class DashboardPageRobot { DashboardPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -58,7 +58,7 @@ class DashboardPageRobot { } Future navigateToSendPage() async { - await commonTestCases.tapItemByKey('dashboard_page_${S.current.buy}_action_button_key'); + await commonTestCases.tapItemByKey('dashboard_page_${S.current.send}_action_button_key'); } Future navigateToSellPage() async { diff --git a/integration_test/robots/disclaimer_page_robot.dart b/integration_test/robots/disclaimer_page_robot.dart index 642f56bca1..18861fc294 100644 --- a/integration_test/robots/disclaimer_page_robot.dart +++ b/integration_test/robots/disclaimer_page_robot.dart @@ -2,7 +2,7 @@ import 'package:cake_wallet/src/screens/disclaimer/disclaimer_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class DisclaimerPageRobot { DisclaimerPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -26,15 +26,14 @@ class DisclaimerPageRobot { } Future tapDisclaimerCheckbox() async { - final checkBox = find.byKey(ValueKey('disclaimer_check_key')); - await tester.tap(checkBox); - await tester.pumpAndSettle(); + await commonTestCases.tapItemByKey('disclaimer_check_key'); + await commonTestCases.defaultSleepTime(); } Future tapAcceptButton() async { - final checkBox = find.byKey(ValueKey('disclaimer_accept_button_key')); - await tester.tap(checkBox); - await tester.pumpAndSettle(); + await commonTestCases.tapItemByKey('disclaimer_accept_button_key'); + + await commonTestCases.defaultSleepTime(); } } diff --git a/integration_test/robots/exchange_confirm_page_robot.dart b/integration_test/robots/exchange_confirm_page_robot.dart index dd2ec68d5b..160fd9dfb6 100644 --- a/integration_test/robots/exchange_confirm_page_robot.dart +++ b/integration_test/robots/exchange_confirm_page_robot.dart @@ -3,7 +3,7 @@ import 'package:cake_wallet/src/screens/exchange_trade/exchange_confirm_page.dar import 'package:flutter/services.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class ExchangeConfirmPageRobot { ExchangeConfirmPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -38,6 +38,8 @@ class ExchangeConfirmPageRobot { } Future onSavedTradeIdButtonPressed() async { + await tester.pumpAndSettle(); + await commonTestCases.defaultSleepTime(); await commonTestCases.tapItemByKey('exchange_confirm_page_saved_id_button_key'); } } diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart index a0d9bd337f..b439e47918 100644 --- a/integration_test/robots/exchange_page_robot.dart +++ b/integration_test/robots/exchange_page_robot.dart @@ -5,7 +5,7 @@ import 'package:cw_core/crypto_currency.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class ExchangePageRobot { ExchangePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -317,6 +317,8 @@ class ExchangePageRobot { } Future handleErrors(String initialAmount) async { + await tester.pumpAndSettle(); + await _handleMinLimitError(initialAmount); await _handleMaxLimitError(initialAmount); diff --git a/integration_test/robots/exchange_trade_page_robot.dart b/integration_test/robots/exchange_trade_page_robot.dart index 30ddb474e1..5708b6faee 100644 --- a/integration_test/robots/exchange_trade_page_robot.dart +++ b/integration_test/robots/exchange_trade_page_robot.dart @@ -6,7 +6,7 @@ import 'package:cake_wallet/src/screens/exchange_trade/exchange_trade_page.dart' import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class ExchangeTradePageRobot { ExchangeTradePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -30,9 +30,10 @@ class ExchangeTradePageRobot { Future onConfirmSendingButtonPressed() async { tester.printToConsole('Now confirming sending'); - final widget = find.byKey(ValueKey('exchange_trade_page_confirm_sending_button_key')); - await tester.tap(widget); - await tester.pump(); + await commonTestCases.tapItemByKey( + 'exchange_trade_page_confirm_sending_button_key', + shouldPumpAndSettle: false, + ); final Completer completer = Completer(); @@ -116,7 +117,7 @@ class ExchangeTradePageRobot { return hasError; } - Future handleSendSuccessOrFailure() async { + Future handleConfirmSendResult() async { bool hasError = false; hasError = await hasErrorWhileSending(); diff --git a/integration_test/robots/new_wallet_type_page_robot.dart b/integration_test/robots/new_wallet_type_page_robot.dart index 16525d9818..89fc8d3901 100644 --- a/integration_test/robots/new_wallet_type_page_robot.dart +++ b/integration_test/robots/new_wallet_type_page_robot.dart @@ -5,7 +5,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class NewWalletTypePageRobot { NewWalletTypePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart index 3b1df0cc77..b6805e9e01 100644 --- a/integration_test/robots/pin_code_widget_robot.dart +++ b/integration_test/robots/pin_code_widget_robot.dart @@ -1,7 +1,7 @@ import 'package:cake_wallet/src/screens/pin_code/pin_code_widget.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class PinCodeWidgetRobot { PinCodeWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -29,7 +29,6 @@ class PinCodeWidgetRobot { } Future enterPinCode(List pinCode, bool isFirstEntry) async { - for (int pin in pinCode) { await pushPinButton(pin); } diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index 28f95df83a..43a65095d8 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -3,7 +3,7 @@ import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class RestoreFromSeedOrKeysPageRobot { RestoreFromSeedOrKeysPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -49,13 +49,17 @@ class RestoreFromSeedOrKeysPageRobot { commonTestCases.hasValueKey('wallet_restore_advanced_settings_button_key'); } - Future enterWalletNameText(String walletName) async { + Future enterWalletNameText(String walletName, {bool isSeedFormEntry = true}) async { await commonTestCases.enterText( - walletName, 'wallet_restore_from_seed_wallet_name_textfield_key'); + walletName, + 'wallet_restore_from_${isSeedFormEntry ? 'seed' : 'keys'}_wallet_name_textfield_key', + ); } - Future selectWalletNameFromAvailableOptions() async { - await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_name_refresh_button_key'); + Future selectWalletNameFromAvailableOptions({bool isSeedFormEntry = true}) async { + await commonTestCases.tapItemByKey( + 'wallet_restore_from_${isSeedFormEntry ? 'seed' : 'keys'}_wallet_name_refresh_button_key', + ); } Future enterSeedPhraseForWalletRestore(String text) async { @@ -70,6 +74,14 @@ class RestoreFromSeedOrKeysPageRobot { await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'); } + Future enterPrivateKeyForWalletRestore(String privateKey) async { + await commonTestCases.enterText( + privateKey, + 'wallet_restore_from_key_private_key_textfield_key', + ); + await tester.pumpAndSettle(); + } + Future onRestoreWalletButtonPressed() async { await commonTestCases.tapItemByKey('wallet_restore_seed_or_key_restore_button_key'); await commonTestCases.defaultSleepTime(); diff --git a/integration_test/robots/restore_options_page_robot.dart b/integration_test/robots/restore_options_page_robot.dart index 2037b913a6..b3cefc90c2 100644 --- a/integration_test/robots/restore_options_page_robot.dart +++ b/integration_test/robots/restore_options_page_robot.dart @@ -1,7 +1,7 @@ import 'package:cake_wallet/src/screens/restore/restore_options_page.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class RestoreOptionsPageRobot { RestoreOptionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -21,19 +21,16 @@ class RestoreOptionsPageRobot { } Future navigateToRestoreFromSeedsPage() async { - tester.printToConsole('Routing to restore from seeds page'); await commonTestCases.tapItemByKey('restore_options_from_seeds_button_key'); await commonTestCases.defaultSleepTime(); } Future navigateToRestoreFromBackupPage() async { - tester.printToConsole('Routing to restore from backup page'); await commonTestCases.tapItemByKey('restore_options_from_backup_button_key'); await commonTestCases.defaultSleepTime(); } Future navigateToRestoreFromHardwareWalletPage() async { - tester.printToConsole('Routing to restore from hardware wallet page'); await commonTestCases.tapItemByKey('restore_options_from_hardware_wallet_button_key'); await commonTestCases.defaultSleepTime(); } diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart new file mode 100644 index 0000000000..68cd73f46d --- /dev/null +++ b/integration_test/robots/send_page_robot.dart @@ -0,0 +1,349 @@ +import 'dart:async'; + +import 'package:cake_wallet/core/execution_state.dart'; +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/send/send_page.dart'; +import 'package:cake_wallet/view_model/send/send_view_model_state.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/transaction_priority.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; +import '../components/common_test_constants.dart'; +import 'auth_page_robot.dart'; + +class SendPageRobot { + SendPageRobot({required this.tester}) + : commonTestCases = CommonTestCases(tester), + authPageRobot = AuthPageRobot(tester); + + WidgetTester tester; + CommonTestCases commonTestCases; + AuthPageRobot authPageRobot; + + Future isSendPage() async { + await commonTestCases.isSpecificPage(); + } + + void hasTitle() { + commonTestCases.hasText(S.current.send); + } + + void confirmViewComponentsDisplayProperly() { + SendPage sendPage = tester.widget(find.byType(SendPage)); + final sendViewModel = sendPage.sendViewModel; + + commonTestCases.hasValueKey('send_page_address_textfield_key'); + commonTestCases.hasValueKey('send_page_note_textfield_key'); + commonTestCases.hasValueKey('send_page_amount_textfield_key'); + commonTestCases.hasValueKey('send_page_add_template_button_key'); + + if (sendViewModel.hasMultipleTokens) { + commonTestCases.hasValueKey('send_page_currency_picker_button_key'); + } + + if (!sendViewModel.isBatchSending) { + commonTestCases.hasValueKey('send_page_send_all_button_key'); + } + + if (!sendViewModel.isFiatDisabled) { + commonTestCases.hasValueKey('send_page_fiat_amount_textfield_key'); + } + + if (sendViewModel.hasFees) { + commonTestCases.hasValueKey('send_page_select_fee_priority_button_key'); + } + + if (sendViewModel.hasCoinControl) { + commonTestCases.hasValueKey('send_page_unspent_coin_button_key'); + } + + if (sendViewModel.hasCurrecyChanger) { + commonTestCases.hasValueKey('send_page_change_asset_button_key'); + } + + if (sendViewModel.sendTemplateViewModel.hasMultiRecipient) { + commonTestCases.hasValueKey('send_page_add_receiver_button_key'); + } + } + + Future selectReceiveCurrency(CryptoCurrency receiveCurrency) async { + final currencyPickerKey = 'send_page_currency_picker_button_key'; + final currencyPickerDialogKey = 'send_page_currency_picker_dialog_button_key'; + + await commonTestCases.tapItemByKey(currencyPickerKey); + commonTestCases.hasValueKey(currencyPickerDialogKey); + + SendPage sendPage = tester.widget(find.byType(SendPage)); + final sendViewModel = sendPage.sendViewModel; + + if (receiveCurrency == sendViewModel.selectedCryptoCurrency) { + await commonTestCases + .tapItemByKey('picker_items_index_${receiveCurrency.name}_selected_item_button_key'); + return; + } + + await commonTestCases.scrollUntilVisible( + 'picker_items_index_${receiveCurrency.name}_button_key', + 'picker_scrollbar_key', + ); + await commonTestCases.defaultSleepTime(); + + await commonTestCases.tapItemByKey('picker_items_index_${receiveCurrency.name}_button_key'); + } + + Future enterReceiveAddress(String receiveAddress) async { + await commonTestCases.enterText(receiveAddress, 'send_page_address_textfield_key'); + await commonTestCases.defaultSleepTime(); + } + + Future enterAmount(String amount) async { + await commonTestCases.enterText(amount, 'send_page_amount_textfield_key'); + } + + Future selectTransactionPriority({TransactionPriority? priority}) async { + SendPage sendPage = tester.widget(find.byType(SendPage)); + final sendViewModel = sendPage.sendViewModel; + + if (!sendViewModel.hasFees || priority == null) return; + + final transactionPriorityPickerKey = 'send_page_select_fee_priority_button_key'; + await commonTestCases.tapItemByKey(transactionPriorityPickerKey); + + if (priority == sendViewModel.transactionPriority) { + await commonTestCases + .tapItemByKey('picker_items_index_${priority.title}_selected_item_button_key'); + return; + } + + await commonTestCases.scrollUntilVisible( + 'picker_items_index_${priority.title}_button_key', + 'picker_scrollbar_key', + ); + await commonTestCases.defaultSleepTime(); + + await commonTestCases.tapItemByKey('picker_items_index_${priority.title}_button_key'); + } + + Future onSendButtonPressed() async { + tester.printToConsole('Pressing send'); + + await commonTestCases.tapItemByKey( + 'send_page_send_button_key', + shouldPumpAndSettle: false, + ); + + await _waitForSendTransactionCompletion(); + + await commonTestCases.defaultSleepTime(); + } + + Future _waitForSendTransactionCompletion() async { + + final Completer completer = Completer(); + + // Loop to wait for the async operation to complete + while (true) { + await Future.delayed(Duration(seconds: 1)); + + tester.printToConsole('Before in auth'); + + await _handleAuthPage(); + + tester.printToConsole('After in auth'); + + final sendPage = tester.widget(find.byType(SendPage)); + final state = sendPage.sendViewModel.state; + + bool isDone = state is ExecutedSuccessfullyState; + bool isFailed = state is FailureState; + + tester.printToConsole('isDone: $isDone'); + tester.printToConsole('isFailed: $isFailed'); + + if (isDone || isFailed) { + tester.printToConsole( + isDone ? 'Completer is done' : 'Completer is done though operation failed', + ); + completer.complete(); + await tester.pump(); + break; + } else { + tester.printToConsole('Completer is not done'); + await tester.pump(); + } + } + + await expectLater(completer.future, completes); + + tester.printToConsole('Done confirming sending operation'); + } + + Future _handleAuthPage() async { + final authPage = authPageRobot.onAuthPage(); + if (authPage) { + tester.printToConsole('Auth'); + await tester.pump(); + + await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + tester.printToConsole('Auth done'); + + await tester.pump(); + + tester.printToConsole('Auth pump done'); + } + } + + Future handleSendResult() async { + tester.printToConsole('Inside handle function'); + + bool hasError = false; + + hasError = await hasErrorWhileSending(); + + tester.printToConsole('Has an Error in the handle: $hasError'); + + int maxRetries = 20; + int retries = 0; + + while (hasError && retries < maxRetries) { + tester.printToConsole('hasErrorInLoop: $hasError'); + await tester.pump(); + + await onSendFailureDialogButtonPressed(); + tester.printToConsole('Failure button tapped'); + + await commonTestCases.defaultSleepTime(); + + await onSendButtonPressed(); + tester.printToConsole('Send button tapped'); + + hasError = await hasErrorWhileSending(); + + retries++; + } + + if (!hasError) { + tester.printToConsole('No error, proceeding with flow'); + await tester.pump(); + } + + await commonTestCases.defaultSleepTime(); + } + + //* ------ On Sending Failure ------------ + Future hasErrorWhileSending() async { + await tester.pump(); + + tester.printToConsole('Checking if there is an error'); + + final errorDialog = find.byKey(ValueKey('send_page_send_failure_dialog_button_key')); + + bool hasError = errorDialog.tryEvaluate(); + + tester.printToConsole('Has error: $hasError'); + + return hasError; + } + + Future onSendFailureDialogButtonPressed() async { + await commonTestCases.defaultSleepTime(); + + tester.printToConsole('Send Button Failure Dialog Triggered'); + + await commonTestCases.tapItemByKey('send_page_send_failure_dialog_button_key'); + } + + //* ------ On Sending Success ------------ + Future onSendButtonOnConfirmSendingDialogPressed() async { + tester.printToConsole('Inside confirm sending dialog: For sending'); + await commonTestCases.defaultSleepTime(); + await tester.pump(); + + final sendText = find.text(S.current.send).last; + bool hasText = sendText.tryEvaluate(); + tester.printToConsole('Has Text: $hasText'); + + if (hasText) { + await commonTestCases.tapItemByFinder(sendText, shouldPumpAndSettle: false); + // Loop to wait for the operation to commit transaction + await _waitForCommitTransactionCompletion(); + + await commonTestCases.defaultSleepTime(seconds: 4); + } else { + await commonTestCases.defaultSleepTime(); + await tester.pump(); + onSendButtonOnConfirmSendingDialogPressed(); + } + } + + Future _waitForCommitTransactionCompletion() async { + final Completer completer = Completer(); + + while (true) { + await Future.delayed(Duration(seconds: 1)); + + final sendPage = tester.widget(find.byType(SendPage)); + final state = sendPage.sendViewModel.state; + + bool isDone = state is TransactionCommitted; + bool isFailed = state is FailureState; + + tester.printToConsole('isDone: $isDone'); + tester.printToConsole('isFailed: $isFailed'); + + if (isDone || isFailed) { + tester.printToConsole( + isDone ? 'Completer is done' : 'Completer is done though operation failed', + ); + completer.complete(); + await tester.pump(); + break; + } else { + tester.printToConsole('Completer is not done'); + await tester.pump(); + } + } + + await expectLater(completer.future, completes); + + tester.printToConsole('Done Committing Transaction'); + } + + Future onCancelButtonOnConfirmSendingDialogPressed() async { + tester.printToConsole('Inside confirm sending dialog: For canceling'); + await commonTestCases.defaultSleepTime(seconds: 4); + + final cancelText = find.text(S.current.cancel); + bool hasText = cancelText.tryEvaluate(); + + if (hasText) { + await commonTestCases.tapItemByFinder(cancelText); + + await commonTestCases.defaultSleepTime(seconds: 4); + } + } + + //* ---- Add Contact Dialog On Send Successful Dialog ----- + Future onSentDialogPopUp() async { + SendPage sendPage = tester.widget(find.byType(SendPage)); + final sendViewModel = sendPage.sendViewModel; + + final newContactAddress = sendPage.newContactAddress ?? sendViewModel.newContactAddress(); + if (newContactAddress != null) { + await _onAddContactButtonOnSentDialogPressed(); + } + + await commonTestCases.defaultSleepTime(); + } + + Future _onAddContactButtonOnSentDialogPressed() async { + await commonTestCases.tapItemByKey('send_page_sent_dialog_add_contact_button_key'); + } + + // ignore: unused_element + Future _onIgnoreButtonOnSentDialogPressed() async { + await commonTestCases.tapItemByKey('send_page_sent_dialog_ignore_button_key'); + } +} diff --git a/integration_test/robots/setup_pin_code_robot.dart b/integration_test/robots/setup_pin_code_robot.dart index 3f3d81c164..0888aac306 100644 --- a/integration_test/robots/setup_pin_code_robot.dart +++ b/integration_test/robots/setup_pin_code_robot.dart @@ -2,7 +2,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/setup_pin_code/setup_pin_code.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; import 'pin_code_widget_robot.dart'; class SetupPinCodeRobot extends PinCodeWidgetRobot { diff --git a/integration_test/robots/welcome_page_robot.dart b/integration_test/robots/welcome_page_robot.dart index 4943666d00..510f63556e 100644 --- a/integration_test/robots/welcome_page_robot.dart +++ b/integration_test/robots/welcome_page_robot.dart @@ -2,7 +2,7 @@ import 'package:cake_wallet/src/screens/welcome/welcome_page.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; -import '../components/common_checks.dart'; +import '../components/common_test_cases.dart'; class WelcomePageRobot { WelcomePageRobot(this.tester) : commonTestCases = CommonTestCases(tester); @@ -24,13 +24,11 @@ class WelcomePageRobot { } Future navigateToCreateNewWalletPage() async { - tester.printToConsole('Routing to create new wallet page'); await commonTestCases.tapItemByKey('welcome_page_create_new_wallet_button_key'); await commonTestCases.defaultSleepTime(); } Future navigateToRestoreWalletPage() async { - tester.printToConsole('Routing to restore wallet page'); await commonTestCases.tapItemByKey('welcome_page_restore_wallet_button_key'); await commonTestCases.defaultSleepTime(); } diff --git a/integration_test/test_suites/exchange_flow_test.dart b/integration_test/test_suites/exchange_flow_test.dart new file mode 100644 index 0000000000..6c993634c5 --- /dev/null +++ b/integration_test/test_suites/exchange_flow_test.dart @@ -0,0 +1,59 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../components/common_test_constants.dart'; +import '../components/common_test_flows.dart'; +import '../robots/auth_page_robot.dart'; +import '../robots/dashboard_page_robot.dart'; +import '../robots/exchange_confirm_page_robot.dart'; +import '../robots/exchange_page_robot.dart'; +import '../robots/exchange_trade_page_robot.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + AuthPageRobot authPageRobot; + CommonTestFlows commonTestFlows; + ExchangePageRobot exchangePageRobot; + DashboardPageRobot dashboardPageRobot; + ExchangeTradePageRobot exchangeTradePageRobot; + ExchangeConfirmPageRobot exchangeConfirmPageRobot; + + group('Exchange Flow Tests', () { + testWidgets('Exchange flow', (tester) async { + authPageRobot = AuthPageRobot(tester); + commonTestFlows = CommonTestFlows(tester); + exchangePageRobot = ExchangePageRobot(tester); + dashboardPageRobot = DashboardPageRobot(tester); + exchangeTradePageRobot = ExchangeTradePageRobot(tester); + exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester); + + await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key')); + await commonTestFlows.restoreWalletThroughSeedsFlow(); + await dashboardPageRobot.navigateToExchangePage(); + + // ----------- Exchange Page ------------- + await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency); + await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency); + + await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount); + await exchangePageRobot.enterDepositRefundAddress( + depositAddress: CommonTestConstants.testWalletAddress, + ); + await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); + + await exchangePageRobot.onExchangeButtonPressed(); + + await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount); + + final onAuthPage = authPageRobot.onAuthPage(); + if (onAuthPage) { + await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + } + + await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); + await exchangeTradePageRobot.onGotItButtonPressed(); + }); + }); +} diff --git a/integration_test/test_suites/send_flow_test.dart b/integration_test/test_suites/send_flow_test.dart new file mode 100644 index 0000000000..38ac1574f7 --- /dev/null +++ b/integration_test/test_suites/send_flow_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../components/common_test_constants.dart'; +import '../components/common_test_flows.dart'; +import '../robots/dashboard_page_robot.dart'; +import '../robots/send_page_robot.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + SendPageRobot sendPageRobot; + CommonTestFlows commonTestFlows; + DashboardPageRobot dashboardPageRobot; + + group('Send Flow Tests', () { + testWidgets('Send flow', (tester) async { + commonTestFlows = CommonTestFlows(tester); + sendPageRobot = SendPageRobot(tester: tester); + dashboardPageRobot = DashboardPageRobot(tester); + + await commonTestFlows.startAppFlow(ValueKey('send_test_app_key')); + await commonTestFlows.restoreWalletThroughSeedsFlow(); + await dashboardPageRobot.navigateToSendPage(); + + await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); + await sendPageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency); + await sendPageRobot.enterAmount(CommonTestConstants.sendTestAmount); + await sendPageRobot.selectTransactionPriority(); + + await sendPageRobot.onSendButtonPressed(); + + await sendPageRobot.handleSendResult(); + + await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed(); + + await sendPageRobot.onSentDialogPopUp(); + }); + }); +} diff --git a/lib/main.dart b/lib/main.dart index 014d5f0119..a7b5d53d91 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -46,7 +46,7 @@ final navigatorKey = GlobalKey(); final rootKey = GlobalKey(); final RouteObserver> routeObserver = RouteObserver>(); -Future main() async { +Future main({Key? topLevelKey}) async { bool isAppRunning = false; await runZonedGuarded(() async { WidgetsFlutterBinding.ensureInitialized(); @@ -67,7 +67,7 @@ Future main() async { await initializeAppConfigs(); - runApp(App()); + runApp(App(key: topLevelKey)); isAppRunning = true; }, (error, stackTrace) async { @@ -253,6 +253,9 @@ Future initialSetup( } class App extends StatefulWidget { + App({this.key}); + + final Key? key; @override AppState createState() => AppState(); } @@ -281,7 +284,7 @@ class AppState extends State with SingleTickerProviderStateMixin { statusBarIconBrightness: statusBarIconBrightness)); return Root( - key: rootKey, + key: widget.key ?? rootKey, appStore: appStore, authenticationStore: authenticationStore, navigatorKey: navigatorKey, diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index 1bc0c5942b..430e309b9c 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -365,7 +365,7 @@ class ExchangeCardState extends State { child: Padding( padding: EdgeInsets.only(top: 20), child: AddressTextField( - key: ValueKey('${_cardInstanceName}_editable_address_textfield_key'), + addressKey: ValueKey('${_cardInstanceName}_editable_address_textfield_key'), focusNode: widget.addressFocusNode, controller: addressController, onURIScanned: (uri) { diff --git a/lib/src/screens/restore/wallet_restore_from_keys_form.dart b/lib/src/screens/restore/wallet_restore_from_keys_form.dart index f8336a2e81..c34e6c9687 100644 --- a/lib/src/screens/restore/wallet_restore_from_keys_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_keys_form.dart @@ -82,10 +82,12 @@ class WalletRestoreFromKeysFromState extends State { alignment: Alignment.centerRight, children: [ BaseTextFormField( + key: ValueKey('wallet_restore_from_keys_wallet_name_textfield_key'), controller: nameTextEditingController, hintText: S.of(context).wallet_name, validator: WalletNameValidator(), suffixIcon: IconButton( + key: ValueKey('wallet_restore_from_keys_wallet_name_refresh_button_key'), onPressed: () async { final rName = await generateName(); FocusManager.instance.primaryFocus?.unfocus(); @@ -132,6 +134,7 @@ class WalletRestoreFromKeysFromState extends State { bool nanoBased = widget.walletRestoreViewModel.type == WalletType.nano || widget.walletRestoreViewModel.type == WalletType.banano; return AddressTextField( + addressKey: ValueKey('wallet_restore_from_key_private_key_textfield_key'), controller: privateKeyController, placeholder: nanoBased ? S.of(context).seed_hex_form : S.of(context).private_key, options: [AddressTextFieldOption.paste], diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index 5ebf0b924b..aa3b743fcc 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -112,7 +112,7 @@ class WalletRestoreFromSeedFormState extends State { controller: nameTextEditingController, hintText: S.of(context).wallet_name, suffixIcon: IconButton( - key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'), + key: ValueKey('wallet_restore_from_seed_wallet_name_refresh_button_key'), onPressed: () async { final rName = await generateName(); FocusManager.instance.primaryFocus?.unfocus(); @@ -145,10 +145,13 @@ class WalletRestoreFromSeedFormState extends State { )), Container(height: 20), SeedWidget( - key: seedWidgetStateKey, - language: language, - type: widget.type, - onSeedChange: onSeedChange), + key: seedWidgetStateKey, + language: language, + type: widget.type, + onSeedChange: onSeedChange, + seedTextFieldKey: ValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'), + pasteButtonKey: ValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'), + ), if (widget.type == WalletType.monero || widget.type == WalletType.wownero) GestureDetector( onTap: () async { diff --git a/lib/src/screens/send/send_page.dart b/lib/src/screens/send/send_page.dart index b46a7f3dbd..2814fdef18 100644 --- a/lib/src/screens/send/send_page.dart +++ b/lib/src/screens/send/send_page.dart @@ -68,11 +68,11 @@ class SendPage extends BasePage { @override Function(BuildContext)? get pushToNextWidget => (context) { - FocusScopeNode currentFocus = FocusScope.of(context); - if (!currentFocus.hasPrimaryFocus) { - currentFocus.focusedChild?.unfocus(); - } - }; + FocusScopeNode currentFocus = FocusScope.of(context); + if (!currentFocus.hasPrimaryFocus) { + currentFocus.focusedChild?.unfocus(); + } + }; @override Widget? leading(BuildContext context) { @@ -246,6 +246,7 @@ class SendPage extends BasePage { return Row( children: [ AddTemplateButton( + key: ValueKey('send_page_add_template_button_key'), onTap: () => Navigator.of(context).pushNamed(Routes.sendTemplate), currentTemplatesLength: templates.length, ), @@ -333,19 +334,22 @@ class SendPage extends BasePage { children: [ if (sendViewModel.hasCurrecyChanger) Observer( - builder: (_) => Padding( - padding: EdgeInsets.only(bottom: 12), - child: PrimaryButton( - onPressed: () => presentCurrencyPicker(context), - text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})', - color: Colors.transparent, - textColor: - Theme.of(context).extension()!.hintTextColor, - ))), + builder: (_) => Padding( + padding: EdgeInsets.only(bottom: 12), + child: PrimaryButton( + key: ValueKey('send_page_change_asset_button_key'), + onPressed: () => presentCurrencyPicker(context), + text: 'Change your asset (${sendViewModel.selectedCryptoCurrency})', + color: Colors.transparent, + textColor: Theme.of(context).extension()!.hintTextColor, + ), + ), + ), if (sendViewModel.sendTemplateViewModel.hasMultiRecipient) Padding( padding: EdgeInsets.only(bottom: 12), child: PrimaryButton( + key: ValueKey('send_page_add_receiver_button_key'), onPressed: () { sendViewModel.addOutput(); Future.delayed(const Duration(milliseconds: 250), () { @@ -362,6 +366,7 @@ class SendPage extends BasePage { Observer( builder: (_) { return LoadingPrimaryButton( + key: ValueKey('send_page_send_button_key'), onPressed: () async { if (_formKey.currentState != null && !_formKey.currentState!.validate()) { if (sendViewModel.outputs.length > 1) { @@ -444,6 +449,8 @@ class SendPage extends BasePage { context: context, builder: (BuildContext context) { return AlertWithOneAction( + key: ValueKey('send_page_send_failure_dialog_key'), + buttonKey: ValueKey('send_page_send_failure_dialog_button_key'), alertTitle: S.of(context).error, alertContent: state.error, buttonText: S.of(context).ok, @@ -459,6 +466,7 @@ class SendPage extends BasePage { context: context, builder: (BuildContext _dialogContext) { return ConfirmSendingAlert( + key: ValueKey('send_page_confirm_sending_dialog_key'), alertTitle: S.of(_dialogContext).confirm_sending, amount: S.of(_dialogContext).send_amount, amountValue: sendViewModel.pendingTransaction!.amountFormatted, @@ -472,6 +480,10 @@ class SendPage extends BasePage { outputs: sendViewModel.outputs, rightButtonText: S.of(_dialogContext).send, leftButtonText: S.of(_dialogContext).cancel, + alertRightActionButtonKey: + ValueKey('send_page_confirm_sending_dialog_send_button_key'), + alertLeftActionButtonKey: + ValueKey('send_page_confirm_sending_dialog_cancel_button_key'), actionRightButton: () async { Navigator.of(_dialogContext).pop(); sendViewModel.commitTransaction(); @@ -505,10 +517,15 @@ class SendPage extends BasePage { if (newContactAddress != null) { return AlertWithTwoActions( + alertDialogKey: ValueKey('send_page_sent_dialog_key'), alertTitle: '', alertContent: alertContent, rightButtonText: S.of(_dialogContext).add_contact, leftButtonText: S.of(_dialogContext).ignor, + alertLeftActionButtonKey: + ValueKey('send_page_sent_dialog_ignore_button_key'), + alertRightActionButtonKey: ValueKey( + 'send_page_sent_dialog_add_contact_button_key'), actionRightButton: () { Navigator.of(_dialogContext).pop(); RequestReviewHandler.requestReview(); diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index ec833159f2..e5cad86c2b 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -157,6 +157,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin _presentPicker(context), child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -298,6 +300,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin GestureDetector( + key: ValueKey('send_page_select_fee_priority_button_key'), onTap: sendViewModel.hasFeesPriority ? () => pickTransactionPriority(context) : () {}, @@ -531,6 +538,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin Navigator.of(context).pushNamed(Routes.unspentCoinsList), child: Container( color: Colors.transparent, @@ -715,6 +723,7 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin( context: context, builder: (_) => CurrencyPicker( + key: ValueKey('send_page_currency_picker_dialog_button_key'), selectedAtIndex: sendViewModel.currencies.indexOf(sendViewModel.selectedCryptoCurrency), items: sendViewModel.currencies, hintText: S.of(context).search_currency, diff --git a/lib/src/widgets/address_text_field.dart b/lib/src/widgets/address_text_field.dart index 044e10f2f6..46e9708f1f 100644 --- a/lib/src/widgets/address_text_field.dart +++ b/lib/src/widgets/address_text_field.dart @@ -15,24 +15,26 @@ import 'package:permission_handler/permission_handler.dart'; enum AddressTextFieldOption { paste, qrCode, addressBook } class AddressTextField extends StatelessWidget { - AddressTextField( - {required this.controller, - this.isActive = true, - this.placeholder, - this.options = const [AddressTextFieldOption.qrCode, AddressTextFieldOption.addressBook], - this.onURIScanned, - this.focusNode, - this.isBorderExist = true, - this.buttonColor, - this.borderColor, - this.iconColor, - this.textStyle, - this.hintStyle, - this.validator, - this.onPushPasteButton, - this.onPushAddressBookButton, - this.onSelectedContact, - this.selectedCurrency, super.key}); + AddressTextField({ + required this.controller, + this.isActive = true, + this.placeholder, + this.options = const [AddressTextFieldOption.qrCode, AddressTextFieldOption.addressBook], + this.onURIScanned, + this.focusNode, + this.isBorderExist = true, + this.buttonColor, + this.borderColor, + this.iconColor, + this.textStyle, + this.hintStyle, + this.validator, + this.onPushPasteButton, + this.onPushAddressBookButton, + this.onSelectedContact, + this.selectedCurrency, + this.addressKey, + }); static const prefixIconWidth = 34.0; static const prefixIconHeight = 34.0; @@ -55,28 +57,27 @@ class AddressTextField extends StatelessWidget { final Function(BuildContext context)? onPushAddressBookButton; final Function(ContactBase contact)? onSelectedContact; final CryptoCurrency? selectedCurrency; + final Key? addressKey; @override Widget build(BuildContext context) { return Stack( children: [ TextFormField( + key: addressKey, enableIMEPersonalizedLearning: false, keyboardType: TextInputType.visiblePassword, onFieldSubmitted: (_) => FocusScope.of(context).unfocus(), enabled: isActive, controller: controller, focusNode: focusNode, - style: textStyle ?? TextStyle( fontSize: 16, color: Theme.of(context).extension()!.titleColor), decoration: InputDecoration( - suffixIcon: SizedBox( width: prefixIconWidth * options.length + (spaceBetweenPrefixIcons * options.length), ), - hintStyle: hintStyle ?? TextStyle(fontSize: 16, color: Theme.of(context).hintColor), hintText: placeholder ?? S.current.widgets_address, focusedBorder: isBorderExist @@ -194,7 +195,7 @@ class AddressTextField extends StatelessWidget { Future _presentQRScanner(BuildContext context) async { bool isCameraPermissionGranted = - await PermissionHandler.checkPermission(Permission.camera, context); + await PermissionHandler.checkPermission(Permission.camera, context); if (!isCameraPermissionGranted) return; final code = await presentQRScanner(); if (code.isEmpty) { diff --git a/lib/src/widgets/alert_with_two_actions.dart b/lib/src/widgets/alert_with_two_actions.dart index ddb11c3ee6..e3d4408a6b 100644 --- a/lib/src/widgets/alert_with_two_actions.dart +++ b/lib/src/widgets/alert_with_two_actions.dart @@ -14,6 +14,9 @@ class AlertWithTwoActions extends BaseAlertDialog { this.isDividerExist = false, // this.leftActionColor, // this.rightActionColor, + this.alertRightActionButtonKey, + this.alertLeftActionButtonKey, + this.alertDialogKey, }); final String alertTitle; @@ -26,6 +29,9 @@ class AlertWithTwoActions extends BaseAlertDialog { // final Color leftActionColor; // final Color rightActionColor; final bool isDividerExist; + final Key? alertRightActionButtonKey; + final Key? alertLeftActionButtonKey; + final Key? alertDialogKey; @override String get titleText => alertTitle; @@ -47,4 +53,13 @@ class AlertWithTwoActions extends BaseAlertDialog { // Color get rightButtonColor => rightActionColor; @override bool get isDividerExists => isDividerExist; + + @override + Key? get dialogKey => alertDialogKey; + + @override + Key? get leftActionButtonKey => alertLeftActionButtonKey; + + @override + Key? get rightActionButtonKey => alertRightActionButtonKey; } diff --git a/lib/src/widgets/base_alert_dialog.dart b/lib/src/widgets/base_alert_dialog.dart index 0f1ca988c8..2e6f1571e7 100644 --- a/lib/src/widgets/base_alert_dialog.dart +++ b/lib/src/widgets/base_alert_dialog.dart @@ -37,6 +37,8 @@ class BaseAlertDialog extends StatelessWidget { Key? rightActionButtonKey; + Key? dialogKey; + Widget title(BuildContext context) { return Text( titleText, @@ -158,6 +160,7 @@ class BaseAlertDialog extends StatelessWidget { @override Widget build(BuildContext context) { return GestureDetector( + key: key, onTap: () => barrierDismissible ? Navigator.of(context).pop() : null, child: Container( color: Colors.transparent, diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index 4c9601e0d6..2d7ade6105 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -4,6 +4,7 @@ import 'dart:math'; import 'package:cake_wallet/src/widgets/search_bar_widget.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cw_core/transaction_priority.dart'; import 'package:flutter/material.dart'; import 'package:cw_core/currency.dart'; import 'package:cake_wallet/src/widgets/picker_wrapper_widget.dart'; @@ -303,11 +304,25 @@ class _PickerState extends State> { ); } + String _getItemName(Item item) { + String itemName; + if (item is Currency) { + itemName = item.name; + } else if (item is TransactionPriority) { + itemName = item.title; + } else { + itemName = ''; + } + + return itemName; + } + Widget buildItem(int index) { final item = widget.headerEnabled ? filteredItems[index] : items[index]; final tag = item is Currency ? item.tag : null; - final currencyName = item is Currency ? item.name : ''; + final itemName = _getItemName(item); + final icon = _getItemIcon(item); final image = images.isNotEmpty ? filteredImages[index] : icon; @@ -327,7 +342,7 @@ class _PickerState extends State> { children: [ Flexible( child: Text( - key: ValueKey('picker_items_index_${currencyName}_text_key'), + key: ValueKey('picker_items_index_${itemName}_text_key'), widget.displayItem?.call(item) ?? item.toString(), softWrap: true, style: TextStyle( @@ -371,7 +386,7 @@ class _PickerState extends State> { ); return GestureDetector( - key: ValueKey('picker_items_index_${currencyName}_button_key'), + key: ValueKey('picker_items_index_${itemName}_button_key'), onTap: () { if (widget.closeOnItemSelected) Navigator.of(context).pop(); onItemSelected(item!); @@ -397,7 +412,7 @@ class _PickerState extends State> { final item = items[index]; final tag = item is Currency ? item.tag : null; - final currencyName = item is Currency ? item.name : ''; + final itemName = _getItemName(item); final icon = _getItemIcon(item); final image = images.isNotEmpty ? images[index] : icon; @@ -418,7 +433,7 @@ class _PickerState extends State> { children: [ Flexible( child: Text( - key: ValueKey('picker_items_index_${currencyName}_selected_item_text_key'), + key: ValueKey('picker_items_index_${itemName}_selected_item_text_key'), widget.displayItem?.call(item) ?? item.toString(), softWrap: true, style: TextStyle( @@ -462,7 +477,7 @@ class _PickerState extends State> { ); return GestureDetector( - key: ValueKey('picker_items_index_${currencyName}_selected_item_button_key'), + key: ValueKey('picker_items_index_${itemName}_selected_item_button_key'), onTap: () { if (widget.closeOnItemSelected) Navigator.of(context).pop(); }, diff --git a/lib/src/widgets/seed_widget.dart b/lib/src/widgets/seed_widget.dart index 72c7ec93a7..1c94718f1a 100644 --- a/lib/src/widgets/seed_widget.dart +++ b/lib/src/widgets/seed_widget.dart @@ -12,9 +12,12 @@ class SeedWidget extends StatefulWidget { required this.language, required this.type, this.onSeedChange, + this.pasteButtonKey, + this.seedTextFieldKey, super.key, }); - + final Key? seedTextFieldKey; + final Key? pasteButtonKey; final String language; final WalletType type; final void Function(String)? onSeedChange; @@ -79,12 +82,11 @@ class SeedWidgetState extends State { top: 10, left: 0, child: Text(S.of(context).enter_seed_phrase, - style: TextStyle( - fontSize: 16.0, color: Theme.of(context).hintColor))), + style: TextStyle(fontSize: 16.0, color: Theme.of(context).hintColor))), Padding( padding: EdgeInsets.only(right: 40, top: 10), child: ValidatableAnnotatedEditableText( - key: ValueKey('wallet_restore_from_seed_wallet_seeds_textfield_key'), + key: widget.seedTextFieldKey, cursorColor: Colors.blue, backgroundCursorColor: Colors.blue, validStyle: TextStyle( @@ -114,16 +116,17 @@ class SeedWidgetState extends State { width: 32, height: 32, child: InkWell( - key: ValueKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'), + key: widget.pasteButtonKey, onTap: () async => _pasteText(), child: Container( padding: EdgeInsets.all(8), decoration: BoxDecoration( color: Theme.of(context).hintColor, - borderRadius: - BorderRadius.all(Radius.circular(6))), + borderRadius: BorderRadius.all(Radius.circular(6))), child: Image.asset('assets/images/paste_ios.png', - color: Theme.of(context).extension()!.textFieldButtonIconColor)), + color: Theme.of(context) + .extension()! + .textFieldButtonIconColor)), ))) ]), Container( From 14735d3d47cbaa1bff90a6c3ff5049e12dbcc84d Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 31 Jul 2024 14:32:16 +0100 Subject: [PATCH 46/69] chore: cleanup --- integration_test/robots/send_page_robot.dart | 36 +++++++++++--------- ios/Podfile.lock | 6 ++++ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index 68cd73f46d..b2ecc4c12e 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -140,28 +140,27 @@ class SendPageRobot { } Future _waitForSendTransactionCompletion() async { - final Completer completer = Completer(); - + // Loop to wait for the async operation to complete while (true) { await Future.delayed(Duration(seconds: 1)); - + tester.printToConsole('Before in auth'); - + await _handleAuthPage(); - + tester.printToConsole('After in auth'); - + final sendPage = tester.widget(find.byType(SendPage)); final state = sendPage.sendViewModel.state; - + bool isDone = state is ExecutedSuccessfullyState; bool isFailed = state is FailureState; - + tester.printToConsole('isDone: $isDone'); tester.printToConsole('isFailed: $isFailed'); - + if (isDone || isFailed) { tester.printToConsole( isDone ? 'Completer is done' : 'Completer is done though operation failed', @@ -174,9 +173,9 @@ class SendPageRobot { await tester.pump(); } } - + await expectLater(completer.future, completes); - + tester.printToConsole('Done confirming sending operation'); } @@ -185,13 +184,18 @@ class SendPageRobot { if (authPage) { tester.printToConsole('Auth'); await tester.pump(); + try { + await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + tester.printToConsole('Auth done'); - await authPageRobot.enterPinCode(CommonTestConstants.pin, false); - tester.printToConsole('Auth done'); - - await tester.pump(); + await tester.pump(); - tester.printToConsole('Auth pump done'); + tester.printToConsole('Auth pump done'); + } catch (e) { + tester.printToConsole('Auth failed, retrying'); + await tester.pump(); + _handleAuthPage(); + } } } diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 5ae4342852..bafbb07634 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -96,6 +96,8 @@ PODS: - shared_preferences_foundation (0.0.1): - Flutter - FlutterMacOS + - sp_scanner (0.0.1): + - Flutter - SwiftProtobuf (1.26.0) - SwiftyGif (5.4.5) - Toast (4.1.1) @@ -135,6 +137,7 @@ DEPENDENCIES: - sensitive_clipboard (from `.symlinks/plugins/sensitive_clipboard/ios`) - share_plus (from `.symlinks/plugins/share_plus/ios`) - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) + - sp_scanner (from `.symlinks/plugins/sp_scanner/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - UnstoppableDomainsResolution (~> 4.0.0) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) @@ -202,6 +205,8 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/share_plus/ios" shared_preferences_foundation: :path: ".symlinks/plugins/shared_preferences_foundation/darwin" + sp_scanner: + :path: ".symlinks/plugins/sp_scanner/ios" uni_links: :path: ".symlinks/plugins/uni_links/ios" url_launcher_ios: @@ -243,6 +248,7 @@ SPEC CHECKSUMS: sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 share_plus: c3fef564749587fc939ef86ffb283ceac0baf9f5 shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 + sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 SwiftProtobuf: 5e8349171e7c2f88f5b9e683cb3cb79d1dc780b3 SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e From 0f91cec0c0e8e52557749793d79ac8e623af4a29 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 1 Aug 2024 13:52:18 +0100 Subject: [PATCH 47/69] ci: Pause CI for now --- .../workflows/automated_integration_test.yml | 446 +++++++++--------- ...nds_related_automated_integration_test.yml | 414 ++++++++-------- 2 files changed, 430 insertions(+), 430 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index 1c8e2f423c..dcfd02f53e 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -1,223 +1,223 @@ -# Commenting out for now, will bring back after I get the CI flow for Automated Testing stable -name: Automated Integration Test - -on: - pull_request: - branches: [main] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" - -jobs: - Automated_integration_test: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - api-level: [29] - arch: [x86, x86_64] - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - PR_NUMBER: ${{ github.event.number }} - - steps: - - name: is pr - if: github.event_name == 'pull_request' - run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV - - - name: is not pr - if: github.event_name != 'pull_request' - run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV - - - name: Free Disk Space (Ubuntu) - uses: insightsengineering/disk-space-reclaimer@v1 - with: - tools-cache: true - android: false - dotnet: true - haskell: true - large-packages: true - swap-storage: true - docker-images: true - - - uses: actions/checkout@v2 - - - name: Setup JDK - uses: actions/setup-java@v1 - with: - java-version: "11.x" - - - name: Flutter action - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.19.6" - channel: stable - - - name: Install package dependencies - run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - - - name: Execute Build and Setup Commands - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} - cd cake_wallet/scripts/android/ - ./install_ndk.sh - source ./app_env.sh cakewallet - chmod +x pubspec_gen.sh - ./app_config.sh - - - name: Cache Externals - id: cache-externals - uses: actions/cache@v3 - with: - path: | - /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/scripts/monero_c/release - key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }} - - - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - name: Generate Externals - run: | - cd /opt/android/cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - - - name: Install Flutter dependencies - run: | - cd /opt/android/cake_wallet - flutter pub get - - - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - - - name: Generate key properties - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - - - name: Generate localization - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_localization.dart - - - name: Build generated code - run: | - cd /opt/android/cake_wallet - ./model_generator.sh - - - name: Add secrets - run: | - cd /opt/android/cake_wallet - touch lib/.secrets.g.dart - touch cw_evm/lib/.secrets.g.dart - touch cw_solana/lib/.secrets.g.dart - touch cw_core/lib/.secrets.g.dart - touch cw_nano/lib/.secrets.g.dart - touch cw_tron/lib/.secrets.g.dart - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - echo "const seeds = '${{ secrets.SEEDS }}';" >> lib/.secrets.g.dart - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart - echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart - echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart - echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart - echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart - echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart - echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart - echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart - echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart - echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - - - name: Rename app - run: | - echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties - - - name: Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - arch: ${{ matrix.arch }} - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: 🚀 Integration tests on Android Emualator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - arch: ${{ matrix.arch }} - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: | - cd /opt/android/cake_wallet - flutter pub get - flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites - if [ $? -ne 0 ]; then - echo "Tests failed" - exit 1 - fi \ No newline at end of file +# # Commenting out for now, will bring back after I get the CI flow for Automated Testing stable +# name: Automated Integration Test + +# on: +# pull_request: +# branches: [main] +# workflow_dispatch: +# inputs: +# branch: +# description: "Branch name to build" +# required: true +# default: "main" + +# jobs: +# Automated_integration_test: +# runs-on: ubuntu-20.04 +# strategy: +# fail-fast: false +# matrix: +# api-level: [29] +# arch: [x86, x86_64] +# env: +# STORE_PASS: test@cake_wallet +# KEY_PASS: test@cake_wallet +# PR_NUMBER: ${{ github.event.number }} + +# steps: +# - name: is pr +# if: github.event_name == 'pull_request' +# run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV + +# - name: is not pr +# if: github.event_name != 'pull_request' +# run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV + +# - name: Free Disk Space (Ubuntu) +# uses: insightsengineering/disk-space-reclaimer@v1 +# with: +# tools-cache: true +# android: false +# dotnet: true +# haskell: true +# large-packages: true +# swap-storage: true +# docker-images: true + +# - uses: actions/checkout@v2 + +# - name: Setup JDK +# uses: actions/setup-java@v1 +# with: +# java-version: "11.x" + +# - name: Flutter action +# uses: subosito/flutter-action@v1 +# with: +# flutter-version: "3.19.6" +# channel: stable + +# - name: Install package dependencies +# run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + +# - name: Execute Build and Setup Commands +# run: | +# sudo mkdir -p /opt/android +# sudo chown $USER /opt/android +# cd /opt/android +# -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +# cargo install cargo-ndk +# git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} +# cd cake_wallet/scripts/android/ +# ./install_ndk.sh +# source ./app_env.sh cakewallet +# chmod +x pubspec_gen.sh +# ./app_config.sh + +# - name: Cache Externals +# id: cache-externals +# uses: actions/cache@v3 +# with: +# path: | +# /opt/android/cake_wallet/cw_haven/android/.cxx +# /opt/android/cake_wallet/scripts/monero_c/release +# key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }} + +# - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} +# name: Generate Externals +# run: | +# cd /opt/android/cake_wallet/scripts/android/ +# source ./app_env.sh cakewallet +# ./build_monero_all.sh + +# - name: Install Flutter dependencies +# run: | +# cd /opt/android/cake_wallet +# flutter pub get + +# - name: Generate KeyStore +# run: | +# cd /opt/android/cake_wallet/android/app +# keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + +# - name: Generate key properties +# run: | +# cd /opt/android/cake_wallet +# flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + +# - name: Generate localization +# run: | +# cd /opt/android/cake_wallet +# flutter packages pub run tool/generate_localization.dart + +# - name: Build generated code +# run: | +# cd /opt/android/cake_wallet +# ./model_generator.sh + +# - name: Add secrets +# run: | +# cd /opt/android/cake_wallet +# touch lib/.secrets.g.dart +# touch cw_evm/lib/.secrets.g.dart +# touch cw_solana/lib/.secrets.g.dart +# touch cw_core/lib/.secrets.g.dart +# touch cw_nano/lib/.secrets.g.dart +# touch cw_tron/lib/.secrets.g.dart +# echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart +# echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart +# echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart +# echo "const seeds = '${{ secrets.SEEDS }}';" >> lib/.secrets.g.dart +# echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart +# echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart +# echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart +# echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart +# echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart +# echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart +# echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart +# echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart +# echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart +# echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart +# echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart +# echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart +# echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart +# echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart +# echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart +# echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart +# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart +# echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart +# echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart +# echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart +# echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart +# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart +# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart +# echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart +# echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart +# echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart +# echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart +# echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart +# echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart +# echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + +# - name: Rename app +# run: | +# echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties + +# - name: Enable KVM +# run: | +# echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules +# sudo udevadm control --reload-rules +# sudo udevadm trigger --name-match=kvm + +# - name: 🦾 Cache gradle +# uses: gradle/actions/setup-gradle@v3 + +# - name: 🦾 Cache AVD +# uses: actions/cache@v4 +# id: avd-cache +# with: +# path: | +# ~/.android/avd/* +# ~/.android/adb* +# key: avd-${{ matrix.api-level }} + +# - name: 🦾 Create AVD and generate snapshot for caching +# if: steps.avd-cache.outputs.cache-hit != 'true' +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# api-level: ${{ matrix.api-level }} +# force-avd-creation: false +# arch: ${{ matrix.arch }} +# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: false +# script: echo "Generated AVD snapshot for caching." + +# - name: 🚀 Integration tests on Android Emualator +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# api-level: ${{ matrix.api-level }} +# force-avd-creation: false +# arch: ${{ matrix.arch }} +# emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: true +# script: | +# cd /opt/android/cake_wallet +# flutter pub get +# flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites +# if [ $? -ne 0 ]; then +# echo "Tests failed" +# exit 1 +# fi \ No newline at end of file diff --git a/.github/workflows/funds_related_automated_integration_test.yml b/.github/workflows/funds_related_automated_integration_test.yml index 1d8752c457..d11abaf198 100644 --- a/.github/workflows/funds_related_automated_integration_test.yml +++ b/.github/workflows/funds_related_automated_integration_test.yml @@ -1,207 +1,207 @@ -name: Funds Related Automated Integration Test - -on: - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" - -jobs: - Funds_related_automated_integration_test: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - api-level: [29] - arch: [x86, x86_64] - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - - steps: - - name: Free Disk Space (Ubuntu) - uses: insightsengineering/disk-space-reclaimer@v1 - with: - tools-cache: true - android: false - dotnet: true - haskell: true - large-packages: true - swap-storage: true - docker-images: true - - - uses: actions/checkout@v2 - - - name: Setup JDK - uses: actions/setup-java@v1 - with: - java-version: "11.x" - - - name: Flutter action - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.19.6" - channel: stable - - - name: Install package dependencies - run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - - - name: Execute Build and Setup Commands - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch main - cd cake_wallet/scripts/android/ - ./install_ndk.sh - source ./app_env.sh cakewallet - chmod +x pubspec_gen.sh - ./app_config.sh - - - name: Cache Externals - id: cache-externals - uses: actions/cache@v3 - with: - path: | - /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/scripts/monero_c/release - key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }} - - - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - name: Generate Externals - run: | - cd /opt/android/cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - - - name: Install Flutter dependencies - run: | - cd /opt/android/cake_wallet - flutter pub get - - - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - - - name: Generate key properties - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - - - name: Generate localization - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_localization.dart - - - name: Build generated code - run: | - cd /opt/android/cake_wallet - ./model_generator.sh - - - name: Add secrets - run: | - cd /opt/android/cake_wallet - touch lib/.secrets.g.dart - touch cw_evm/lib/.secrets.g.dart - touch cw_solana/lib/.secrets.g.dart - touch cw_core/lib/.secrets.g.dart - touch cw_nano/lib/.secrets.g.dart - touch cw_tron/lib/.secrets.g.dart - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - echo "const seeds = '${{ secrets.SEEDS }}';" >> lib/.secrets.g.dart - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart - echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart - echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart - echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart - echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart - echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart - echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart - echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart - echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart - echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - - - name: 🦾 Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - arch: ${{ matrix.arch }} - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: 🚀 Integration tests on Android Emualator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - arch: ${{ matrix.arch }} - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: | - cd /opt/android/cake_wallet - flutter pub get - flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart - if [ $? -ne 0 ]; then - echo "Tests failed" - exit 1 - fi \ No newline at end of file +# name: Funds Related Automated Integration Test + +# on: +# workflow_dispatch: +# inputs: +# branch: +# description: "Branch name to build" +# required: true +# default: "main" + +# jobs: +# Funds_related_automated_integration_test: +# runs-on: ubuntu-20.04 +# strategy: +# fail-fast: false +# matrix: +# api-level: [29] +# arch: [x86, x86_64] +# env: +# STORE_PASS: test@cake_wallet +# KEY_PASS: test@cake_wallet + +# steps: +# - name: Free Disk Space (Ubuntu) +# uses: insightsengineering/disk-space-reclaimer@v1 +# with: +# tools-cache: true +# android: false +# dotnet: true +# haskell: true +# large-packages: true +# swap-storage: true +# docker-images: true + +# - uses: actions/checkout@v2 + +# - name: Setup JDK +# uses: actions/setup-java@v1 +# with: +# java-version: "11.x" + +# - name: Flutter action +# uses: subosito/flutter-action@v1 +# with: +# flutter-version: "3.19.6" +# channel: stable + +# - name: Install package dependencies +# run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + +# - name: Execute Build and Setup Commands +# run: | +# sudo mkdir -p /opt/android +# sudo chown $USER /opt/android +# cd /opt/android +# -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh +# cargo install cargo-ndk +# git clone https://github.com/cake-tech/cake_wallet.git --branch main +# cd cake_wallet/scripts/android/ +# ./install_ndk.sh +# source ./app_env.sh cakewallet +# chmod +x pubspec_gen.sh +# ./app_config.sh + +# - name: Cache Externals +# id: cache-externals +# uses: actions/cache@v3 +# with: +# path: | +# /opt/android/cake_wallet/cw_haven/android/.cxx +# /opt/android/cake_wallet/scripts/monero_c/release +# key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }} + +# - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} +# name: Generate Externals +# run: | +# cd /opt/android/cake_wallet/scripts/android/ +# source ./app_env.sh cakewallet +# ./build_monero_all.sh + +# - name: Install Flutter dependencies +# run: | +# cd /opt/android/cake_wallet +# flutter pub get + +# - name: Generate KeyStore +# run: | +# cd /opt/android/cake_wallet/android/app +# keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + +# - name: Generate key properties +# run: | +# cd /opt/android/cake_wallet +# flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + +# - name: Generate localization +# run: | +# cd /opt/android/cake_wallet +# flutter packages pub run tool/generate_localization.dart + +# - name: Build generated code +# run: | +# cd /opt/android/cake_wallet +# ./model_generator.sh + +# - name: Add secrets +# run: | +# cd /opt/android/cake_wallet +# touch lib/.secrets.g.dart +# touch cw_evm/lib/.secrets.g.dart +# touch cw_solana/lib/.secrets.g.dart +# touch cw_core/lib/.secrets.g.dart +# touch cw_nano/lib/.secrets.g.dart +# touch cw_tron/lib/.secrets.g.dart +# echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart +# echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart +# echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart +# echo "const seeds = '${{ secrets.SEEDS }}';" >> lib/.secrets.g.dart +# echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart +# echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart +# echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart +# echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart +# echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart +# echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart +# echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart +# echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart +# echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart +# echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart +# echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart +# echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart +# echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart +# echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart +# echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart +# echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart +# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart +# echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart +# echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart +# echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart +# echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart +# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart +# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart +# echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart +# echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart +# echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart +# echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart +# echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart +# echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart +# echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart +# echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + +# - name: 🦾 Enable KVM +# run: | +# echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules +# sudo udevadm control --reload-rules +# sudo udevadm trigger --name-match=kvm + +# - name: 🦾 Cache gradle +# uses: gradle/actions/setup-gradle@v3 + +# - name: 🦾 Cache AVD +# uses: actions/cache@v4 +# id: avd-cache +# with: +# path: | +# ~/.android/avd/* +# ~/.android/adb* +# key: avd-${{ matrix.api-level }} + +# - name: 🦾 Create AVD and generate snapshot for caching +# if: steps.avd-cache.outputs.cache-hit != 'true' +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# api-level: ${{ matrix.api-level }} +# force-avd-creation: false +# arch: ${{ matrix.arch }} +# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: false +# script: echo "Generated AVD snapshot for caching." + +# - name: 🚀 Integration tests on Android Emualator +# uses: reactivecircus/android-emulator-runner@v2 +# with: +# api-level: ${{ matrix.api-level }} +# force-avd-creation: false +# arch: ${{ matrix.arch }} +# emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none +# disable-animations: true +# script: | +# cd /opt/android/cake_wallet +# flutter pub get +# flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart +# if [ $? -ne 0 ]; then +# echo "Tests failed" +# exit 1 +# fi \ No newline at end of file From 5a9091585ec00c67c0a391747bf6d0e0f1b2642d Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 1 Aug 2024 13:54:56 +0100 Subject: [PATCH 48/69] ci: Pause CI for now --- .../workflows/automated_integration_test.yml | 50 +++++++++---------- 1 file changed, 25 insertions(+), 25 deletions(-) diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml index dcfd02f53e..8fbe61300f 100644 --- a/.github/workflows/automated_integration_test.yml +++ b/.github/workflows/automated_integration_test.yml @@ -1,28 +1,28 @@ -# # Commenting out for now, will bring back after I get the CI flow for Automated Testing stable -# name: Automated Integration Test - -# on: -# pull_request: -# branches: [main] -# workflow_dispatch: -# inputs: -# branch: -# description: "Branch name to build" -# required: true -# default: "main" - -# jobs: -# Automated_integration_test: -# runs-on: ubuntu-20.04 -# strategy: -# fail-fast: false -# matrix: -# api-level: [29] -# arch: [x86, x86_64] -# env: -# STORE_PASS: test@cake_wallet -# KEY_PASS: test@cake_wallet -# PR_NUMBER: ${{ github.event.number }} +# Commenting out for now, will bring back after I get the CI flow for Automated Testing stable +name: Automated Integration Test + +on: + pull_request: + branches: [main] + workflow_dispatch: + inputs: + branch: + description: "Branch name to build" + required: true + default: "main" + +jobs: + Automated_integration_test: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + api-level: [29] + arch: [x86, x86_64] + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + PR_NUMBER: ${{ github.event.number }} # steps: # - name: is pr From f41e604304ccf60c48f02b8ab9701cc4d4e4a21b Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 1 Aug 2024 13:59:03 +0100 Subject: [PATCH 49/69] ci: Pause CI for now --- .../workflows/automated_integration_test.yml | 223 ------------------ ...nds_related_automated_integration_test.yml | 207 ---------------- 2 files changed, 430 deletions(-) delete mode 100644 .github/workflows/automated_integration_test.yml delete mode 100644 .github/workflows/funds_related_automated_integration_test.yml diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml deleted file mode 100644 index 8fbe61300f..0000000000 --- a/.github/workflows/automated_integration_test.yml +++ /dev/null @@ -1,223 +0,0 @@ -# Commenting out for now, will bring back after I get the CI flow for Automated Testing stable -name: Automated Integration Test - -on: - pull_request: - branches: [main] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" - -jobs: - Automated_integration_test: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - api-level: [29] - arch: [x86, x86_64] - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - PR_NUMBER: ${{ github.event.number }} - -# steps: -# - name: is pr -# if: github.event_name == 'pull_request' -# run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV - -# - name: is not pr -# if: github.event_name != 'pull_request' -# run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV - -# - name: Free Disk Space (Ubuntu) -# uses: insightsengineering/disk-space-reclaimer@v1 -# with: -# tools-cache: true -# android: false -# dotnet: true -# haskell: true -# large-packages: true -# swap-storage: true -# docker-images: true - -# - uses: actions/checkout@v2 - -# - name: Setup JDK -# uses: actions/setup-java@v1 -# with: -# java-version: "11.x" - -# - name: Flutter action -# uses: subosito/flutter-action@v1 -# with: -# flutter-version: "3.19.6" -# channel: stable - -# - name: Install package dependencies -# run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - -# - name: Execute Build and Setup Commands -# run: | -# sudo mkdir -p /opt/android -# sudo chown $USER /opt/android -# cd /opt/android -# -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -# cargo install cargo-ndk -# git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} -# cd cake_wallet/scripts/android/ -# ./install_ndk.sh -# source ./app_env.sh cakewallet -# chmod +x pubspec_gen.sh -# ./app_config.sh - -# - name: Cache Externals -# id: cache-externals -# uses: actions/cache@v3 -# with: -# path: | -# /opt/android/cake_wallet/cw_haven/android/.cxx -# /opt/android/cake_wallet/scripts/monero_c/release -# key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }} - -# - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} -# name: Generate Externals -# run: | -# cd /opt/android/cake_wallet/scripts/android/ -# source ./app_env.sh cakewallet -# ./build_monero_all.sh - -# - name: Install Flutter dependencies -# run: | -# cd /opt/android/cake_wallet -# flutter pub get - -# - name: Generate KeyStore -# run: | -# cd /opt/android/cake_wallet/android/app -# keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - -# - name: Generate key properties -# run: | -# cd /opt/android/cake_wallet -# flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - -# - name: Generate localization -# run: | -# cd /opt/android/cake_wallet -# flutter packages pub run tool/generate_localization.dart - -# - name: Build generated code -# run: | -# cd /opt/android/cake_wallet -# ./model_generator.sh - -# - name: Add secrets -# run: | -# cd /opt/android/cake_wallet -# touch lib/.secrets.g.dart -# touch cw_evm/lib/.secrets.g.dart -# touch cw_solana/lib/.secrets.g.dart -# touch cw_core/lib/.secrets.g.dart -# touch cw_nano/lib/.secrets.g.dart -# touch cw_tron/lib/.secrets.g.dart -# echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart -# echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart -# echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart -# echo "const seeds = '${{ secrets.SEEDS }}';" >> lib/.secrets.g.dart -# echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart -# echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart -# echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart -# echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart -# echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart -# echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart -# echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart -# echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart -# echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart -# echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart -# echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart -# echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart -# echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart -# echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart -# echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart -# echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart -# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart -# echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart -# echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart -# echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart -# echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart -# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart -# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart -# echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart -# echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart -# echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart -# echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart -# echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart -# echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart -# echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - -# - name: Rename app -# run: | -# echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties - -# - name: Enable KVM -# run: | -# echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules -# sudo udevadm control --reload-rules -# sudo udevadm trigger --name-match=kvm - -# - name: 🦾 Cache gradle -# uses: gradle/actions/setup-gradle@v3 - -# - name: 🦾 Cache AVD -# uses: actions/cache@v4 -# id: avd-cache -# with: -# path: | -# ~/.android/avd/* -# ~/.android/adb* -# key: avd-${{ matrix.api-level }} - -# - name: 🦾 Create AVD and generate snapshot for caching -# if: steps.avd-cache.outputs.cache-hit != 'true' -# uses: reactivecircus/android-emulator-runner@v2 -# with: -# api-level: ${{ matrix.api-level }} -# force-avd-creation: false -# arch: ${{ matrix.arch }} -# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -# disable-animations: false -# script: echo "Generated AVD snapshot for caching." - -# - name: 🚀 Integration tests on Android Emualator -# uses: reactivecircus/android-emulator-runner@v2 -# with: -# api-level: ${{ matrix.api-level }} -# force-avd-creation: false -# arch: ${{ matrix.arch }} -# emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -# disable-animations: true -# script: | -# cd /opt/android/cake_wallet -# flutter pub get -# flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites -# if [ $? -ne 0 ]; then -# echo "Tests failed" -# exit 1 -# fi \ No newline at end of file diff --git a/.github/workflows/funds_related_automated_integration_test.yml b/.github/workflows/funds_related_automated_integration_test.yml deleted file mode 100644 index d11abaf198..0000000000 --- a/.github/workflows/funds_related_automated_integration_test.yml +++ /dev/null @@ -1,207 +0,0 @@ -# name: Funds Related Automated Integration Test - -# on: -# workflow_dispatch: -# inputs: -# branch: -# description: "Branch name to build" -# required: true -# default: "main" - -# jobs: -# Funds_related_automated_integration_test: -# runs-on: ubuntu-20.04 -# strategy: -# fail-fast: false -# matrix: -# api-level: [29] -# arch: [x86, x86_64] -# env: -# STORE_PASS: test@cake_wallet -# KEY_PASS: test@cake_wallet - -# steps: -# - name: Free Disk Space (Ubuntu) -# uses: insightsengineering/disk-space-reclaimer@v1 -# with: -# tools-cache: true -# android: false -# dotnet: true -# haskell: true -# large-packages: true -# swap-storage: true -# docker-images: true - -# - uses: actions/checkout@v2 - -# - name: Setup JDK -# uses: actions/setup-java@v1 -# with: -# java-version: "11.x" - -# - name: Flutter action -# uses: subosito/flutter-action@v1 -# with: -# flutter-version: "3.19.6" -# channel: stable - -# - name: Install package dependencies -# run: sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - -# - name: Execute Build and Setup Commands -# run: | -# sudo mkdir -p /opt/android -# sudo chown $USER /opt/android -# cd /opt/android -# -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -# cargo install cargo-ndk -# git clone https://github.com/cake-tech/cake_wallet.git --branch main -# cd cake_wallet/scripts/android/ -# ./install_ndk.sh -# source ./app_env.sh cakewallet -# chmod +x pubspec_gen.sh -# ./app_config.sh - -# - name: Cache Externals -# id: cache-externals -# uses: actions/cache@v3 -# with: -# path: | -# /opt/android/cake_wallet/cw_haven/android/.cxx -# /opt/android/cake_wallet/scripts/monero_c/release -# key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh') }} - -# - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} -# name: Generate Externals -# run: | -# cd /opt/android/cake_wallet/scripts/android/ -# source ./app_env.sh cakewallet -# ./build_monero_all.sh - -# - name: Install Flutter dependencies -# run: | -# cd /opt/android/cake_wallet -# flutter pub get - -# - name: Generate KeyStore -# run: | -# cd /opt/android/cake_wallet/android/app -# keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - -# - name: Generate key properties -# run: | -# cd /opt/android/cake_wallet -# flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - -# - name: Generate localization -# run: | -# cd /opt/android/cake_wallet -# flutter packages pub run tool/generate_localization.dart - -# - name: Build generated code -# run: | -# cd /opt/android/cake_wallet -# ./model_generator.sh - -# - name: Add secrets -# run: | -# cd /opt/android/cake_wallet -# touch lib/.secrets.g.dart -# touch cw_evm/lib/.secrets.g.dart -# touch cw_solana/lib/.secrets.g.dart -# touch cw_core/lib/.secrets.g.dart -# touch cw_nano/lib/.secrets.g.dart -# touch cw_tron/lib/.secrets.g.dart -# echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart -# echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart -# echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart -# echo "const seeds = '${{ secrets.SEEDS }}';" >> lib/.secrets.g.dart -# echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart -# echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart -# echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart -# echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart -# echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart -# echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart -# echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart -# echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart -# echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart -# echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart -# echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart -# echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart -# echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart -# echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart -# echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart -# echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart -# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart -# echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart -# echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart -# echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart -# echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart -# echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart -# echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart -# echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart -# echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart -# echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart -# echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart -# echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart -# echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart -# echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart -# echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - -# - name: 🦾 Enable KVM -# run: | -# echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules -# sudo udevadm control --reload-rules -# sudo udevadm trigger --name-match=kvm - -# - name: 🦾 Cache gradle -# uses: gradle/actions/setup-gradle@v3 - -# - name: 🦾 Cache AVD -# uses: actions/cache@v4 -# id: avd-cache -# with: -# path: | -# ~/.android/avd/* -# ~/.android/adb* -# key: avd-${{ matrix.api-level }} - -# - name: 🦾 Create AVD and generate snapshot for caching -# if: steps.avd-cache.outputs.cache-hit != 'true' -# uses: reactivecircus/android-emulator-runner@v2 -# with: -# api-level: ${{ matrix.api-level }} -# force-avd-creation: false -# arch: ${{ matrix.arch }} -# emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -# disable-animations: false -# script: echo "Generated AVD snapshot for caching." - -# - name: 🚀 Integration tests on Android Emualator -# uses: reactivecircus/android-emulator-runner@v2 -# with: -# api-level: ${{ matrix.api-level }} -# force-avd-creation: false -# arch: ${{ matrix.arch }} -# emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none -# disable-animations: true -# script: | -# cd /opt/android/cake_wallet -# flutter pub get -# flutter drive --driver=test_driver/integration_test.dart --target=integration_test/app_test.dart -# if [ $? -ne 0 ]; then -# echo "Tests failed" -# exit 1 -# fi \ No newline at end of file From 1169b416d9b254329f0c2cbbb80c6c262579c529 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 9 Aug 2024 13:42:29 +0100 Subject: [PATCH 50/69] test: Restore wallets integration automated tests --- .../components/common_test_flows.dart | 138 ++++++++++++++++-- integration_test/funds_related_tests.dart | 6 +- .../robots/dashboard_menu_widget_robot.dart | 32 ++++ .../robots/dashboard_page_robot.dart | 34 ++++- .../robots/restore_options_page_robot.dart | 6 +- .../robots/wallet_list_page_robot.dart | 27 ++++ .../test_suites/exchange_flow_test.dart | 6 +- ...estore_wallet_through_seeds_flow_test.dart | 57 ++++++++ .../test_suites/send_flow_test.dart | 6 +- lib/src/screens/dashboard/dashboard_page.dart | 2 +- .../dashboard/pages/nft_details_page.dart | 5 +- .../dashboard/widgets/menu_widget.dart | 3 +- .../screens/restore/restore_options_page.dart | 2 +- .../screens/wallet_list/wallet_list_page.dart | 2 + lib/src/widgets/setting_actions.dart | 11 ++ tool/utils/secret_key.dart | 21 +++ 16 files changed, 328 insertions(+), 30 deletions(-) create mode 100644 integration_test/robots/dashboard_menu_widget_robot.dart create mode 100644 integration_test/robots/wallet_list_page_robot.dart create mode 100644 integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index a7eb1b17ed..a35543fcba 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -1,23 +1,28 @@ +import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; -import 'package:cake_wallet/.secrets.g.dart' as secrets; import 'package:cake_wallet/main.dart' as app; +import '../robots/dashboard_page_robot.dart'; import '../robots/disclaimer_page_robot.dart'; import '../robots/new_wallet_type_page_robot.dart'; import '../robots/restore_from_seed_or_key_robot.dart'; import '../robots/restore_options_page_robot.dart'; import '../robots/setup_pin_code_robot.dart'; +import '../robots/wallet_list_page_robot.dart'; import '../robots/welcome_page_robot.dart'; import 'common_test_cases.dart'; import 'common_test_constants.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; class CommonTestFlows { CommonTestFlows(this._tester) : _commonTestCases = CommonTestCases(_tester), _welcomePageRobot = WelcomePageRobot(_tester), _setupPinCodeRobot = SetupPinCodeRobot(_tester), + _dashboardPageRobot = DashboardPageRobot(_tester), + _walletListPageRobot = WalletListPageRobot(_tester), _disclaimerPageRobot = DisclaimerPageRobot(_tester), _newWalletTypePageRobot = NewWalletTypePageRobot(_tester), _restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester), @@ -28,11 +33,67 @@ class CommonTestFlows { final WelcomePageRobot _welcomePageRobot; final SetupPinCodeRobot _setupPinCodeRobot; + final DashboardPageRobot _dashboardPageRobot; final DisclaimerPageRobot _disclaimerPageRobot; + final WalletListPageRobot _walletListPageRobot; final NewWalletTypePageRobot _newWalletTypePageRobot; final RestoreOptionsPageRobot _restoreOptionsPageRobot; final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot; + String getWalletSeedsByWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.monero: + return secrets.moneroTestWalletSeeds; + case WalletType.bitcoin: + return secrets.bitcoinTestWalletSeeds; + case WalletType.ethereum: + return secrets.ethereumTestWalletSeeds; + case WalletType.litecoin: + return secrets.litecoinTestWalletSeeds; + case WalletType.bitcoinCash: + return secrets.bitcoinCashTestWalletSeeds; + case WalletType.polygon: + return secrets.polygonTestWalletSeeds; + case WalletType.solana: + return secrets.solanaTestWalletSeeds; + case WalletType.tron: + return secrets.tronTestWalletSeeds; + case WalletType.nano: + return secrets.nanoTestWalletSeeds; + case WalletType.wownero: + return secrets.wowneroTestWalletSeeds; + default: + return ''; + } + } + + String getReceiveAddressByWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.monero: + return secrets.moneroTestWalletReceiveAddress; + case WalletType.bitcoin: + return secrets.bitcoinTestWalletReceiveAddress; + case WalletType.ethereum: + return secrets.ethereumTestWalletReceiveAddress; + case WalletType.litecoin: + return secrets.litecoinTestWalletReceiveAddress; + case WalletType.bitcoinCash: + return secrets.bitcoinCashTestWalletReceiveAddress; + case WalletType.polygon: + return secrets.polygonTestWalletReceiveAddress; + case WalletType.solana: + return secrets.solanaTestWalletReceiveAddress; + case WalletType.tron: + return secrets.tronTestWalletReceiveAddress; + case WalletType.nano: + return secrets.nanoTestWalletReceiveAddress; + case WalletType.wownero: + return secrets.wowneroTestWalletReceiveAddress; + default: + return ''; + } + } + Future startAppFlow(Key key) async { await app.main(topLevelKey: ValueKey('send_flow_test_app_key')); @@ -46,23 +107,61 @@ class CommonTestFlows { await _disclaimerPageRobot.tapAcceptButton(); } - Future restoreWalletThroughSeedsFlow() async { - await _welcomeToRestoreFromSeedsPath(); - await _restoreFromSeeds(); + Future welcomePageToRestoreWalletThroughSeedsFlow( + WalletType walletTypeToRestore, + String walletSeed, + ) async { + await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore); + await _restoreFromSeeds(walletSeed); } - Future restoreWalletThroughKeysFlow() async { - await _welcomeToRestoreFromSeedsPath(); + Future welcomePageToRestoreWalletThroughKeysFlow( + WalletType walletTypeToRestore, + ) async { + await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore); await _restoreFromKeys(); } - Future _welcomeToRestoreFromSeedsPath() async { + Future switchAndRestoreWalletFromDashboardPage( + WalletType walletType, + String walletSeed, + ) async { + _tester.printToConsole('Switching to Wallet Menu'); + await switchToWalletMenuFromDashboardPage(); + + _tester.printToConsole('Restoring ${walletType.name} Wallet'); + await restoreWalletFromWalletMenu(walletType, walletSeed); + } + + Future switchToWalletMenuFromDashboardPage() async { + await _dashboardPageRobot.openDrawerMenu(); + await _commonTestCases.defaultSleepTime(); + + await _dashboardPageRobot.dashboardMenuWidgetRobot.navigateToWalletMenu(); + await _commonTestCases.defaultSleepTime(); + } + + Future restoreWalletFromWalletMenu(WalletType walletType, String walletSeed) async { + await _walletListPageRobot.navigateToRestoreWalletOptionsPage(); + await _commonTestCases.defaultSleepTime(); + + await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); + await _commonTestCases.defaultSleepTime(); + + await _selectWalletTypeForWallet(walletType); + await _commonTestCases.defaultSleepTime(); + + await _restoreFromSeeds(walletSeed); + await _commonTestCases.defaultSleepTime(); + } + + Future _welcomeToRestoreFromSeedsOrKeysPath(WalletType walletTypeToRestore) async { // --------- Welcome Page --------------- await _welcomePageRobot.navigateToRestoreWalletPage(); // ----------- Restore Options Page ----------- - // Route to restore from seeds page to continue flow - await _restoreOptionsPageRobot.navigateToRestoreFromSeedsPage(); + // Route to restore from seeds or keys page to continue flow + await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); // ----------- SetupPinCode Page ------------- // Confirm initial defaults - Widgets to be displayed etc @@ -72,20 +171,25 @@ class CommonTestFlows { await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, false); await _setupPinCodeRobot.tapSuccessButton(); + await _selectWalletTypeForWallet(walletTypeToRestore); + } + + Future _selectWalletTypeForWallet(WalletType type) async { // ----------- NewWalletType Page ------------- // Confirm scroll behaviour works properly - await _newWalletTypePageRobot - .findParticularWalletTypeInScrollableList(CommonTestConstants.testWalletType); + await _newWalletTypePageRobot.findParticularWalletTypeInScrollableList(type); // Select a wallet and route to next page - await _newWalletTypePageRobot.selectWalletType(CommonTestConstants.testWalletType); + await _newWalletTypePageRobot.selectWalletType(type); await _newWalletTypePageRobot.onNextButtonPressed(); } - Future _restoreFromSeeds() async { + // Main Restore Actions - On the RestoreFromSeed/Keys Page + Future _restoreFromSeeds(String walletSeed) async { // ----------- RestoreFromSeedOrKeys Page ------------- - await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName); - await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.seeds); + + await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions(); + await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(walletSeed); await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); } @@ -93,7 +197,9 @@ class CommonTestFlows { await _commonTestCases.swipePage(); await _commonTestCases.defaultSleepTime(); - await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName); + await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions( + isSeedFormEntry: false, + ); await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(''); await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart index 9d97d47f84..caebe8b49e 100644 --- a/integration_test/funds_related_tests.dart +++ b/integration_test/funds_related_tests.dart @@ -9,6 +9,7 @@ import 'robots/dashboard_page_robot.dart'; import 'robots/exchange_confirm_page_robot.dart'; import 'robots/exchange_page_robot.dart'; import 'robots/exchange_trade_page_robot.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -32,7 +33,10 @@ void main() { await commonTestFlows.startAppFlow(ValueKey('funds_exchange_test_app_key')); - await commonTestFlows.restoreWalletThroughSeedsFlow(); + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( + CommonTestConstants.testWalletType, + secrets.solanaTestWalletSeeds, + ); // ----------- RestoreFromSeedOrKeys Page ------------- await dashboardPageRobot.navigateToExchangePage(); diff --git a/integration_test/robots/dashboard_menu_widget_robot.dart b/integration_test/robots/dashboard_menu_widget_robot.dart new file mode 100644 index 0000000000..830f436b4d --- /dev/null +++ b/integration_test/robots/dashboard_menu_widget_robot.dart @@ -0,0 +1,32 @@ +import 'package:cake_wallet/src/screens/dashboard/widgets/menu_widget.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class DashboardMenuWidgetRobot { + DashboardMenuWidgetRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future hasMenuWidget() async { + commonTestCases.hasType(); + } + + void displaysTheCorrectWalletNameAndSubName() { + final menuWidgetState = tester.state(find.byType(MenuWidget)); + + final walletName = menuWidgetState.widget.dashboardViewModel.name; + commonTestCases.hasText(walletName); + + final walletSubName = menuWidgetState.widget.dashboardViewModel.subname; + if (walletSubName.isNotEmpty) { + commonTestCases.hasText(walletSubName); + } + } + + Future navigateToWalletMenu() async { + await commonTestCases.tapItemByKey('dashboard_page_menu_widget_wallet_menu_button_key'); + await commonTestCases.defaultSleepTime(); + } +} diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart index fc917c3b2d..81f6514a5b 100644 --- a/integration_test/robots/dashboard_page_robot.dart +++ b/integration_test/robots/dashboard_page_robot.dart @@ -1,20 +1,44 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/dashboard/dashboard_page.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/balance_page.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter_test/flutter_test.dart'; import '../components/common_test_cases.dart'; +import 'dashboard_menu_widget_robot.dart'; class DashboardPageRobot { - DashboardPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + DashboardPageRobot(this.tester) + : commonTestCases = CommonTestCases(tester), + dashboardMenuWidgetRobot = DashboardMenuWidgetRobot(tester); final WidgetTester tester; + final DashboardMenuWidgetRobot dashboardMenuWidgetRobot; late CommonTestCases commonTestCases; Future isDashboardPage() async { await commonTestCases.isSpecificPage(); } + Future confirmWalletTypeIsDisplayedCorrectly( + WalletType type, { + bool isHaven = false, + }) async { + final cryptoBalanceWidget = + tester.widget(find.byType(CryptoBalanceWidget)); + final hasAccounts = cryptoBalanceWidget.dashboardViewModel.balanceViewModel.hasAccounts; + + if (hasAccounts) { + final walletName = cryptoBalanceWidget.dashboardViewModel.name; + commonTestCases.hasText(walletName); + } else { + final walletName = walletTypeToString(type); + final assetName = isHaven ? '$walletName Assets' : walletName; + commonTestCases.hasText(assetName); + } + await commonTestCases.defaultSleepTime(seconds: 5); + } + void confirmServiceUpdateButtonDisplays() { commonTestCases.hasValueKey('dashboard_page_services_update_button_key'); } @@ -30,9 +54,7 @@ class DashboardPageRobot { Future confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type, {bool isHaven = false}) async { //Balance Page - final walletName = walletTypeToString(type); - final assetName = isHaven ? '$walletName Assets' : walletName; - commonTestCases.hasText(assetName); + await confirmWalletTypeIsDisplayedCorrectly(type, isHaven: isHaven); // Swipe to Cake features Page await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false); @@ -53,6 +75,10 @@ class DashboardPageRobot { await commonTestCases.defaultSleepTime(seconds: 5); } + Future openDrawerMenu() async { + await commonTestCases.tapItemByKey('dashboard_page_wallet_menu_button_key'); + } + Future navigateToBuyPage() async { await commonTestCases.tapItemByKey('dashboard_page_${S.current.buy}_action_button_key'); } diff --git a/integration_test/robots/restore_options_page_robot.dart b/integration_test/robots/restore_options_page_robot.dart index b3cefc90c2..cd19196091 100644 --- a/integration_test/robots/restore_options_page_robot.dart +++ b/integration_test/robots/restore_options_page_robot.dart @@ -14,14 +14,14 @@ class RestoreOptionsPageRobot { } void hasRestoreOptionsButton() { - commonTestCases.hasValueKey('restore_options_from_seeds_button_key'); + commonTestCases.hasValueKey('restore_options_from_seeds_or_keys_button_key'); commonTestCases.hasValueKey('restore_options_from_backup_button_key'); commonTestCases.hasValueKey('restore_options_from_hardware_wallet_button_key'); commonTestCases.hasValueKey('restore_options_from_qr_button_key'); } - Future navigateToRestoreFromSeedsPage() async { - await commonTestCases.tapItemByKey('restore_options_from_seeds_button_key'); + Future navigateToRestoreFromSeedsOrKeysPage() async { + await commonTestCases.tapItemByKey('restore_options_from_seeds_or_keys_button_key'); await commonTestCases.defaultSleepTime(); } diff --git a/integration_test/robots/wallet_list_page_robot.dart b/integration_test/robots/wallet_list_page_robot.dart new file mode 100644 index 0000000000..b46d4ca954 --- /dev/null +++ b/integration_test/robots/wallet_list_page_robot.dart @@ -0,0 +1,27 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class WalletListPageRobot { + WalletListPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isWalletListPage() async { + await commonTestCases.isSpecificPage(); + } + + void displaysCorrectTitle() { + commonTestCases.hasText(S.current.wallets); + } + + Future navigateToCreateNewWalletPage() async { + commonTestCases.tapItemByKey('wallet_list_page_create_new_wallet_button_key'); + } + + Future navigateToRestoreWalletOptionsPage() async { + commonTestCases.tapItemByKey('wallet_list_page_restore_wallet_button_key'); + } +} diff --git a/integration_test/test_suites/exchange_flow_test.dart b/integration_test/test_suites/exchange_flow_test.dart index 6c993634c5..333506ef83 100644 --- a/integration_test/test_suites/exchange_flow_test.dart +++ b/integration_test/test_suites/exchange_flow_test.dart @@ -9,6 +9,7 @@ import '../robots/dashboard_page_robot.dart'; import '../robots/exchange_confirm_page_robot.dart'; import '../robots/exchange_page_robot.dart'; import '../robots/exchange_trade_page_robot.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -30,7 +31,10 @@ void main() { exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester); await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key')); - await commonTestFlows.restoreWalletThroughSeedsFlow(); + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( + CommonTestConstants.testWalletType, + secrets.solanaTestWalletSeeds, + ); await dashboardPageRobot.navigateToExchangePage(); // ----------- Exchange Page ------------- diff --git a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart new file mode 100644 index 0000000000..bd1d1f07b0 --- /dev/null +++ b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart @@ -0,0 +1,57 @@ +import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../components/common_test_flows.dart'; +import '../robots/dashboard_page_robot.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + CommonTestFlows commonTestFlows; + DashboardPageRobot dashboardPageRobot; + + testWidgets( + 'Restoring Wallets Through Seeds', + (tester) async { + commonTestFlows = CommonTestFlows(tester); + dashboardPageRobot = DashboardPageRobot(tester); + + // Start the app + await commonTestFlows.startAppFlow( + ValueKey('restore_wallets_through_seeds_test_app_key'), + ); + + // Restore the first wallet type: Solana + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( + WalletType.solana, + secrets.solanaTestWalletSeeds, + ); + + // Confirm it actually restores a solana wallet + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana); + + // Do the same for other available wallet types + for (var walletType in availableWalletTypes) { + if (walletType == WalletType.solana || walletType == WalletType.wownero) { + continue; + } + + await commonTestFlows.switchAndRestoreWalletFromDashboardPage( + walletType, + commonTestFlows.getWalletSeedsByWalletType(walletType), + ); + + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType); + } + + // Go to the wallet menu, provides a visual confirmation that all the wallets were correctly restored + await commonTestFlows.switchToWalletMenuFromDashboardPage(); + + await Future.delayed(Duration(seconds: 5)); + }, + ); +} diff --git a/integration_test/test_suites/send_flow_test.dart b/integration_test/test_suites/send_flow_test.dart index 38ac1574f7..4ad902b833 100644 --- a/integration_test/test_suites/send_flow_test.dart +++ b/integration_test/test_suites/send_flow_test.dart @@ -6,6 +6,7 @@ import '../components/common_test_constants.dart'; import '../components/common_test_flows.dart'; import '../robots/dashboard_page_robot.dart'; import '../robots/send_page_robot.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); @@ -21,7 +22,10 @@ void main() { dashboardPageRobot = DashboardPageRobot(tester); await commonTestFlows.startAppFlow(ValueKey('send_test_app_key')); - await commonTestFlows.restoreWalletThroughSeedsFlow(); + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( + CommonTestConstants.testWalletType, + secrets.solanaTestWalletSeeds, + ); await dashboardPageRobot.navigateToSendPage(); await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); diff --git a/lib/src/screens/dashboard/dashboard_page.dart b/lib/src/screens/dashboard/dashboard_page.dart index b34ae303f5..ac7a8d1f1f 100644 --- a/lib/src/screens/dashboard/dashboard_page.dart +++ b/lib/src/screens/dashboard/dashboard_page.dart @@ -140,7 +140,7 @@ class _DashboardPageView extends BasePage { bool get resizeToAvoidBottomInset => false; @override - Widget get endDrawer => MenuWidget(dashboardViewModel); + Widget get endDrawer => MenuWidget(dashboardViewModel, ValueKey('dashboard_page_drawer_menu_widget_key')); @override Widget leading(BuildContext context) { diff --git a/lib/src/screens/dashboard/pages/nft_details_page.dart b/lib/src/screens/dashboard/pages/nft_details_page.dart index 15d2a2b5c3..b8352a6721 100644 --- a/lib/src/screens/dashboard/pages/nft_details_page.dart +++ b/lib/src/screens/dashboard/pages/nft_details_page.dart @@ -28,7 +28,10 @@ class NFTDetailsPage extends BasePage { bool get resizeToAvoidBottomInset => false; @override - Widget get endDrawer => MenuWidget(dashboardViewModel); + Widget get endDrawer => MenuWidget( + dashboardViewModel, + ValueKey('nft_details_page_menu_widget_key'), + ); @override Widget trailing(BuildContext context) { diff --git a/lib/src/screens/dashboard/widgets/menu_widget.dart b/lib/src/screens/dashboard/widgets/menu_widget.dart index 78d8abc95c..bcc3099804 100644 --- a/lib/src/screens/dashboard/widgets/menu_widget.dart +++ b/lib/src/screens/dashboard/widgets/menu_widget.dart @@ -9,7 +9,7 @@ import 'package:cw_core/wallet_type.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class MenuWidget extends StatefulWidget { - MenuWidget(this.dashboardViewModel); + MenuWidget(this.dashboardViewModel, Key? key); final DashboardViewModel dashboardViewModel; @@ -191,6 +191,7 @@ class MenuWidgetState extends State { final isLastTile = index == itemCount - 1; return SettingActionButton( + key: item.key, isLastTile: isLastTile, tileHeight: tileHeight, selectionActive: false, diff --git a/lib/src/screens/restore/restore_options_page.dart b/lib/src/screens/restore/restore_options_page.dart index cb5086fe16..9ec6e66114 100644 --- a/lib/src/screens/restore/restore_options_page.dart +++ b/lib/src/screens/restore/restore_options_page.dart @@ -59,7 +59,7 @@ class RestoreOptionsPage extends BasePage { child: Column( children: [ OptionTile( - key: ValueKey('restore_options_from_seeds_button_key'), + key: ValueKey('restore_options_from_seeds_or_keys_button_key'), onPressed: () => Navigator.pushNamed( context, Routes.restoreWalletFromSeedKeys, diff --git a/lib/src/screens/wallet_list/wallet_list_page.dart b/lib/src/screens/wallet_list/wallet_list_page.dart index 2a48416086..dab0a1d233 100644 --- a/lib/src/screens/wallet_list/wallet_list_page.dart +++ b/lib/src/screens/wallet_list/wallet_list_page.dart @@ -239,6 +239,7 @@ class WalletListBodyState extends State { child: Column( children: [ PrimaryImageButton( + key: ValueKey('wallet_list_page_create_new_wallet_button_key'), onPressed: () { //TODO(David): Find a way to optimize this if (isSingleCoin) { @@ -276,6 +277,7 @@ class WalletListBodyState extends State { ), SizedBox(height: 10.0), PrimaryImageButton( + key: ValueKey('wallet_list_page_restore_wallet_button_key'), onPressed: () { if (widget.walletListViewModel.shouldRequireTOTP2FAForCreatingNewWallets) { widget.authService.authenticateAction( diff --git a/lib/src/widgets/setting_actions.dart b/lib/src/widgets/setting_actions.dart index 272ed57c2b..1578d27d78 100644 --- a/lib/src/widgets/setting_actions.dart +++ b/lib/src/widgets/setting_actions.dart @@ -5,9 +5,11 @@ import 'package:flutter/material.dart'; class SettingActions { final String Function(BuildContext) name; final String image; + final Key key; final void Function(BuildContext) onTap; SettingActions._({ + required this.key, required this.name, required this.image, required this.onTap, @@ -38,6 +40,7 @@ class SettingActions { ]; static SettingActions silentPaymentsSettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_silent_payment_settings_button_key'), name: (context) => S.of(context).silent_payments_settings, image: 'assets/images/bitcoin_menu.png', onTap: (BuildContext context) { @@ -47,6 +50,7 @@ class SettingActions { ); static SettingActions connectionSettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_connection_and_sync_settings_button_key'), name: (context) => S.of(context).connection_sync, image: 'assets/images/nodes_menu.png', onTap: (BuildContext context) { @@ -56,6 +60,7 @@ class SettingActions { ); static SettingActions walletSettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_wallet_menu_button_key'), name: (context) => S.of(context).wallets, image: 'assets/images/wallet_menu.png', onTap: (BuildContext context) { @@ -65,6 +70,7 @@ class SettingActions { ); static SettingActions addressBookSettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_address_book_button_key'), name: (context) => S.of(context).address_book_menu, image: 'assets/images/open_book_menu.png', onTap: (BuildContext context) { @@ -74,6 +80,7 @@ class SettingActions { ); static SettingActions securityBackupSettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_security_and_backup_button_key'), name: (context) => S.of(context).security_and_backup, image: 'assets/images/key_menu.png', onTap: (BuildContext context) { @@ -83,6 +90,7 @@ class SettingActions { ); static SettingActions privacySettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_privacy_settings_button_key'), name: (context) => S.of(context).privacy, image: 'assets/images/privacy_menu.png', onTap: (BuildContext context) { @@ -92,6 +100,7 @@ class SettingActions { ); static SettingActions displaySettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_display_settings_button_key'), name: (context) => S.of(context).display_settings, image: 'assets/images/eye_menu.png', onTap: (BuildContext context) { @@ -101,6 +110,7 @@ class SettingActions { ); static SettingActions otherSettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_other_settings_button_key'), name: (context) => S.of(context).other_settings, image: 'assets/images/settings_menu.png', onTap: (BuildContext context) { @@ -110,6 +120,7 @@ class SettingActions { ); static SettingActions supportSettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_support_settings_button_key'), name: (context) => S.of(context).settings_support, image: 'assets/images/question_mark.png', onTap: (BuildContext context) { diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index a317b8e1be..1a22baf7c3 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -44,6 +44,27 @@ class SecretKey { SecretKey('cakePayApiKey', () => ''), SecretKey('CSRFToken', () => ''), SecretKey('authorization', () => ''), + SecretKey('moneroTestWalletSeeds', () => ''), + SecretKey('bitcoinTestWalletSeeds', () => ''), + SecretKey('ethereumTestWalletSeeds', () => ''), + SecretKey('litecoinTestWalletSeeds', () => ''), + SecretKey('bitcoinCashTestWalletSeeds', () => ''), + SecretKey('polygonTestWalletSeeds', () => ''), + SecretKey('solanaTestWalletSeeds', () => ''), + SecretKey('polygonTestWalletSeeds', () => ''), + SecretKey('tronTestWalletSeeds', () => ''), + SecretKey('nanoTestWalletSeeds', () => ''), + SecretKey('wowneroTestWalletSeeds', () => ''), + SecretKey('moneroTestWalletReceiveAddress', () => ''), + SecretKey('bitcoinTestWalletReceiveAddress', () => ''), + SecretKey('ethereumTestWalletReceiveAddress', () => ''), + SecretKey('litecoinTestWalletReceiveAddress', () => ''), + SecretKey('bitcoinCashTestWalletReceiveAddress', () => ''), + SecretKey('polygonTestWalletReceiveAddress', () => ''), + SecretKey('solanaTestWalletReceiveAddress', () => ''), + SecretKey('tronTestWalletReceiveAddress', () => ''), + SecretKey('nanoTestWalletReceiveAddress', () => ''), + SecretKey('wowneroTestWalletReceiveAddress', () => ''), ]; static final evmChainsSecrets = [ From ea3dbc8fb685222900ab0e92a52d477468d59008 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Mon, 12 Aug 2024 08:08:44 +0100 Subject: [PATCH 51/69] Fix: Add keys back to currency amount textfield widget --- .../exchange/widgets/exchange_card.dart | 88 +++++++++++-------- .../receive/widgets/currency_input_field.dart | 26 +++++- lib/src/screens/send/widgets/send_card.dart | 11 ++- 3 files changed, 81 insertions(+), 44 deletions(-) diff --git a/lib/src/screens/exchange/widgets/exchange_card.dart b/lib/src/screens/exchange/widgets/exchange_card.dart index a11da96bfa..75a2eadd7f 100644 --- a/lib/src/screens/exchange/widgets/exchange_card.dart +++ b/lib/src/screens/exchange/widgets/exchange_card.dart @@ -19,33 +19,33 @@ import 'package:cake_wallet/src/screens/exchange/widgets/currency_picker.dart'; import 'package:cake_wallet/themes/extensions/send_page_theme.dart'; class ExchangeCard extends StatefulWidget { - ExchangeCard( - {Key? key, - required this.initialCurrency, - required this.initialAddress, - required this.initialWalletName, - required this.initialIsAmountEditable, - required this.isAmountEstimated, - required this.currencies, - required this.onCurrencySelected, - this.imageArrow, - this.currencyValueValidator, - this.addressTextFieldValidator, - this.title = '', - this.initialIsAddressEditable = true, - this.hasRefundAddress = false, - this.isMoneroWallet = false, - this.currencyButtonColor = Colors.transparent, - this.addressButtonsColor = Colors.transparent, - this.borderColor = Colors.transparent, - this.hasAllAmount = false, - this.isAllAmountEnabled = false, - this.amountFocusNode, - this.addressFocusNode, - this.allAmount, - this.onPushPasteButton, - this.onPushAddressBookButton, - this.onDispose, + ExchangeCard({ + Key? key, + required this.initialCurrency, + required this.initialAddress, + required this.initialWalletName, + required this.initialIsAmountEditable, + required this.isAmountEstimated, + required this.currencies, + required this.onCurrencySelected, + this.imageArrow, + this.currencyValueValidator, + this.addressTextFieldValidator, + this.title = '', + this.initialIsAddressEditable = true, + this.hasRefundAddress = false, + this.isMoneroWallet = false, + this.currencyButtonColor = Colors.transparent, + this.addressButtonsColor = Colors.transparent, + this.borderColor = Colors.transparent, + this.hasAllAmount = false, + this.isAllAmountEnabled = false, + this.amountFocusNode, + this.addressFocusNode, + this.allAmount, + this.onPushPasteButton, + this.onPushAddressBookButton, + this.onDispose, required this.cardInstanceName, }) : super(key: key); @@ -199,17 +199,26 @@ class ExchangeCardState extends State { ], ), CurrencyAmountTextField( - imageArrow: widget.imageArrow, - selectedCurrency: _selectedCurrency.toString(), - amountFocusNode: widget.amountFocusNode, - amountController: amountController, - onTapPicker: () => _presentPicker(context), - isAmountEditable: _isAmountEditable, - isPickerEnable: true, - allAmountButton: widget.hasAllAmount, - currencyValueValidator: widget.currencyValueValidator, - tag: _selectedCurrency.tag, - allAmountCallback: widget.allAmount), + currencyPickerButtonKey: ValueKey('${_cardInstanceName}_currency_picker_button_key'), + selectedCurrencyTextKey: ValueKey('${_cardInstanceName}_selected_currency_text_key'), + selectedCurrencyTagTextKey: + ValueKey('${_cardInstanceName}_selected_currency_tag_text_key'), + amountTextfieldKey: ValueKey('${_cardInstanceName}_amount_textfield_key'), + sendAllButtonKey: ValueKey('${_cardInstanceName}_send_all_button_key'), + currencyAmountTextFieldWidgetKey: + ValueKey('${_cardInstanceName}_currency_amount_textfield_widget_key'), + imageArrow: widget.imageArrow, + selectedCurrency: _selectedCurrency.toString(), + amountFocusNode: widget.amountFocusNode, + amountController: amountController, + onTapPicker: () => _presentPicker(context), + isAmountEditable: _isAmountEditable, + isPickerEnable: true, + allAmountButton: widget.hasAllAmount, + currencyValueValidator: widget.currencyValueValidator, + tag: _selectedCurrency.tag, + allAmountCallback: widget.allAmount, + ), Divider(height: 1, color: Theme.of(context).extension()!.textFieldHintColor), Padding( padding: EdgeInsets.only(top: 5), @@ -362,7 +371,8 @@ class ExchangeCardState extends State { child: Semantics( label: S.of(context).copy_address, child: InkWell( - key: ValueKey('${_cardInstanceName}_copy_refund_address_button_key'), + key: ValueKey( + '${_cardInstanceName}_copy_refund_address_button_key'), onTap: () { Clipboard.setData( ClipboardData(text: addressController.text)); diff --git a/lib/src/screens/receive/widgets/currency_input_field.dart b/lib/src/screens/receive/widgets/currency_input_field.dart index ce3de9a6c7..382c53f9d5 100644 --- a/lib/src/screens/receive/widgets/currency_input_field.dart +++ b/lib/src/screens/receive/widgets/currency_input_field.dart @@ -24,8 +24,20 @@ class CurrencyAmountTextField extends StatelessWidget { this.tagBackgroundColor, this.currencyValueValidator, this.allAmountCallback, - }); + this.sendAllButtonKey, + this.amountTextfieldKey, + this.currencyPickerButtonKey, + this.selectedCurrencyTextKey, + this.selectedCurrencyTagTextKey, + this.currencyAmountTextFieldWidgetKey, + }) : super(key: currencyAmountTextFieldWidgetKey); + final Key? sendAllButtonKey; + final Key? amountTextfieldKey; + final Key? currencyPickerButtonKey; + final Key? selectedCurrencyTextKey; + final Key? selectedCurrencyTagTextKey; + final Key? currencyAmountTextFieldWidgetKey; final Widget? imageArrow; final String selectedCurrency; final String? tag; @@ -54,6 +66,7 @@ class CurrencyAmountTextField extends StatelessWidget { ? Container( height: 32, child: InkWell( + key: currencyPickerButtonKey, onTap: onTapPicker, child: Row( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -65,6 +78,7 @@ class CurrencyAmountTextField extends StatelessWidget { Image.asset('assets/images/arrow_bottom_purple_icon.png', color: textColor, height: 8)), Text( + key: selectedCurrencyTextKey, selectedCurrency, style: TextStyle( fontWeight: FontWeight.w600, @@ -77,6 +91,7 @@ class CurrencyAmountTextField extends StatelessWidget { ), ) : Text( + key: selectedCurrencyTextKey, selectedCurrency, style: TextStyle( fontWeight: FontWeight.w600, @@ -98,6 +113,7 @@ class CurrencyAmountTextField extends StatelessWidget { child: Padding( padding: const EdgeInsets.all(6.0), child: Text( + key: selectedCurrencyTagTextKey, tag!, style: TextStyle( fontSize: 12, @@ -132,9 +148,9 @@ class CurrencyAmountTextField extends StatelessWidget { padding: EdgeInsets.symmetric(vertical: 4, horizontal: 8), margin: const EdgeInsets.only(right: 3), decoration: BoxDecoration( - border: Border.all( - color: textColor, - ), + border: Border.all( + color: textColor, + ), borderRadius: BorderRadius.circular(26), color: Theme.of(context).primaryColor)) : _prefixContent, @@ -146,6 +162,7 @@ class CurrencyAmountTextField extends StatelessWidget { child: FocusTraversalOrder( order: NumericFocusOrder(1), child: BaseTextFormField( + key: amountTextfieldKey, focusNode: amountFocusNode, controller: amountController, enabled: isAmountEditable, @@ -184,6 +201,7 @@ class CurrencyAmountTextField extends StatelessWidget { borderRadius: const BorderRadius.all(Radius.circular(6)), ), child: InkWell( + key: sendAllButtonKey, onTap: allAmountCallback, child: Center( child: Text( diff --git a/lib/src/screens/send/widgets/send_card.dart b/lib/src/screens/send/widgets/send_card.dart index 0000d28eac..83aa7704ae 100644 --- a/lib/src/screens/send/widgets/send_card.dart +++ b/lib/src/screens/send/widgets/send_card.dart @@ -210,6 +210,11 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin with AutomaticKeepAliveClientMixin _presentPicker(context), isPickerEnable: sendViewModel.hasMultipleTokens, tag: sendViewModel.selectedCryptoCurrency.tag, - allAmountButton: !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL, + allAmountButton: + !sendViewModel.isBatchSending && sendViewModel.shouldDisplaySendALL, currencyValueValidator: output.sendAll ? sendViewModel.allAmountValidator : sendViewModel.amountValidator, @@ -258,6 +264,9 @@ class SendCardState extends State with AutomaticKeepAliveClientMixin Date: Tue, 13 Aug 2024 13:27:54 +0100 Subject: [PATCH 52/69] fix: Switch variable name --- integration_test/components/common_test_flows.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index a7eb1b17ed..807509de97 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -85,7 +85,7 @@ class CommonTestFlows { Future _restoreFromSeeds() async { // ----------- RestoreFromSeedOrKeys Page ------------- await _restoreFromSeedOrKeysPageRobot.enterWalletNameText(CommonTestConstants.testWalletName); - await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.seeds); + await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(secrets.solanaTestWalletSeeds); await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); } From 9ade090035bdc86e790ef5c4278a4d364194caf5 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 13 Aug 2024 13:41:02 +0100 Subject: [PATCH 53/69] fix: remove automation for now --- .github/workflows/pr_test_build_android.yml | 32 --------------------- 1 file changed, 32 deletions(-) diff --git a/.github/workflows/pr_test_build_android.yml b/.github/workflows/pr_test_build_android.yml index 6166d1f8a5..0b8309aada 100644 --- a/.github/workflows/pr_test_build_android.yml +++ b/.github/workflows/pr_test_build_android.yml @@ -214,35 +214,3 @@ jobs: title: "${{ env.BRANCH_NAME }}.apk" filename: ${{ env.BRANCH_NAME }}.apk initial_comment: ${{ github.event.head_commit.message }} - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: 🚀 Integration tests on Android Emualator - timeout-minutes: 30 - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: flutter test integration_test From b6474ae621edc2060c9845df777044565e345700 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 13 Aug 2024 18:30:52 +0100 Subject: [PATCH 54/69] tests: Automated tests for Create wallets flow --- .../components/common_test_flows.dart | 248 ++++++++++++------ integration_test/funds_related_tests.dart | 1 + .../robots/new_wallet_page_robot.dart | 35 +++ .../robots/pre_seed_page_robot.dart | 20 ++ .../robots/wallet_seed_page_robot.dart | 57 ++++ .../test_suites/create_wallet_flow_test.dart | 55 ++++ .../test_suites/exchange_flow_test.dart | 3 +- ...estore_wallet_through_seeds_flow_test.dart | 6 +- .../test_suites/send_flow_test.dart | 1 + lib/src/screens/InfoPage.dart | 13 +- .../screens/new_wallet/new_wallet_page.dart | 17 +- lib/src/screens/seed/pre_seed_page.dart | 10 +- lib/src/screens/seed/wallet_seed_page.dart | 95 ++++--- .../setup_2fa/setup_2fa_info_page.dart | 8 +- lib/src/widgets/seed_language_selector.dart | 11 +- 15 files changed, 442 insertions(+), 138 deletions(-) create mode 100644 integration_test/robots/new_wallet_page_robot.dart create mode 100644 integration_test/robots/pre_seed_page_robot.dart create mode 100644 integration_test/robots/wallet_seed_page_robot.dart create mode 100644 integration_test/test_suites/create_wallet_flow_test.dart diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index a35543fcba..14eacf7e6a 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -6,24 +6,29 @@ import 'package:cake_wallet/main.dart' as app; import '../robots/dashboard_page_robot.dart'; import '../robots/disclaimer_page_robot.dart'; +import '../robots/new_wallet_page_robot.dart'; import '../robots/new_wallet_type_page_robot.dart'; +import '../robots/pre_seed_page_robot.dart'; import '../robots/restore_from_seed_or_key_robot.dart'; import '../robots/restore_options_page_robot.dart'; import '../robots/setup_pin_code_robot.dart'; import '../robots/wallet_list_page_robot.dart'; +import '../robots/wallet_seed_page_robot.dart'; import '../robots/welcome_page_robot.dart'; import 'common_test_cases.dart'; -import 'common_test_constants.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; class CommonTestFlows { CommonTestFlows(this._tester) : _commonTestCases = CommonTestCases(_tester), _welcomePageRobot = WelcomePageRobot(_tester), + _preSeedPageRobot = PreSeedPageRobot(_tester), _setupPinCodeRobot = SetupPinCodeRobot(_tester), _dashboardPageRobot = DashboardPageRobot(_tester), - _walletListPageRobot = WalletListPageRobot(_tester), + _newWalletPageRobot = NewWalletPageRobot(_tester), _disclaimerPageRobot = DisclaimerPageRobot(_tester), + _walletSeedPageRobot = WalletSeedPageRobot(_tester), + _walletListPageRobot = WalletListPageRobot(_tester), _newWalletTypePageRobot = NewWalletTypePageRobot(_tester), _restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester), _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester); @@ -32,71 +37,21 @@ class CommonTestFlows { final CommonTestCases _commonTestCases; final WelcomePageRobot _welcomePageRobot; + final PreSeedPageRobot _preSeedPageRobot; final SetupPinCodeRobot _setupPinCodeRobot; + final NewWalletPageRobot _newWalletPageRobot; final DashboardPageRobot _dashboardPageRobot; final DisclaimerPageRobot _disclaimerPageRobot; + final WalletSeedPageRobot _walletSeedPageRobot; final WalletListPageRobot _walletListPageRobot; final NewWalletTypePageRobot _newWalletTypePageRobot; final RestoreOptionsPageRobot _restoreOptionsPageRobot; final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot; - String getWalletSeedsByWalletType(WalletType walletType) { - switch (walletType) { - case WalletType.monero: - return secrets.moneroTestWalletSeeds; - case WalletType.bitcoin: - return secrets.bitcoinTestWalletSeeds; - case WalletType.ethereum: - return secrets.ethereumTestWalletSeeds; - case WalletType.litecoin: - return secrets.litecoinTestWalletSeeds; - case WalletType.bitcoinCash: - return secrets.bitcoinCashTestWalletSeeds; - case WalletType.polygon: - return secrets.polygonTestWalletSeeds; - case WalletType.solana: - return secrets.solanaTestWalletSeeds; - case WalletType.tron: - return secrets.tronTestWalletSeeds; - case WalletType.nano: - return secrets.nanoTestWalletSeeds; - case WalletType.wownero: - return secrets.wowneroTestWalletSeeds; - default: - return ''; - } - } - - String getReceiveAddressByWalletType(WalletType walletType) { - switch (walletType) { - case WalletType.monero: - return secrets.moneroTestWalletReceiveAddress; - case WalletType.bitcoin: - return secrets.bitcoinTestWalletReceiveAddress; - case WalletType.ethereum: - return secrets.ethereumTestWalletReceiveAddress; - case WalletType.litecoin: - return secrets.litecoinTestWalletReceiveAddress; - case WalletType.bitcoinCash: - return secrets.bitcoinCashTestWalletReceiveAddress; - case WalletType.polygon: - return secrets.polygonTestWalletReceiveAddress; - case WalletType.solana: - return secrets.solanaTestWalletReceiveAddress; - case WalletType.tron: - return secrets.tronTestWalletReceiveAddress; - case WalletType.nano: - return secrets.nanoTestWalletReceiveAddress; - case WalletType.wownero: - return secrets.wowneroTestWalletReceiveAddress; - default: - return ''; - } - } - + //* ========== Handles flow to start the app afresh and accept disclaimer ============= Future startAppFlow(Key key) async { await app.main(topLevelKey: ValueKey('send_flow_test_app_key')); - + await _tester.pumpAndSettle(); // --------- Disclaimer Page ------------ @@ -107,41 +62,69 @@ class CommonTestFlows { await _disclaimerPageRobot.tapAcceptButton(); } + //* ========== Handles flow from welcome to creating a new wallet =============== + Future welcomePageToCreateNewWalletFlow( + WalletType walletTypeToCreate, + List walletPin, + ) async { + await _welcomeToCreateWalletPath(walletTypeToCreate, walletPin); + + await _generateNewWalletDetails(); + + await _confirmPreSeedInfo(); + + await _confirmWalletDetails(); + } + + //* ========== Handles flow from welcome to restoring wallet from seeds =============== Future welcomePageToRestoreWalletThroughSeedsFlow( WalletType walletTypeToRestore, String walletSeed, + List walletPin, ) async { - await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore); + await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin); await _restoreFromSeeds(walletSeed); } + //* ========== Handles flow from welcome to restoring wallet from keys =============== Future welcomePageToRestoreWalletThroughKeysFlow( WalletType walletTypeToRestore, + List walletPin, ) async { - await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore); + await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin); await _restoreFromKeys(); } - Future switchAndRestoreWalletFromDashboardPage( - WalletType walletType, - String walletSeed, - ) async { + //* ========== Handles switching to wallet list or menu from dashboard =============== + Future switchToWalletMenuFromDashboardPage() async { _tester.printToConsole('Switching to Wallet Menu'); - await switchToWalletMenuFromDashboardPage(); + await _dashboardPageRobot.openDrawerMenu(); + await _commonTestCases.defaultSleepTime(); - _tester.printToConsole('Restoring ${walletType.name} Wallet'); - await restoreWalletFromWalletMenu(walletType, walletSeed); + await _dashboardPageRobot.dashboardMenuWidgetRobot.navigateToWalletMenu(); + await _commonTestCases.defaultSleepTime(); } - Future switchToWalletMenuFromDashboardPage() async { - await _dashboardPageRobot.openDrawerMenu(); + //* ========== Handles creating new wallet flow from wallet list/menu =============== + Future createNewWalletFromWalletMenu(WalletType walletTypeToCreate) async { + _tester.printToConsole('Creating ${walletTypeToCreate.name} Wallet'); + await _walletListPageRobot.navigateToCreateNewWalletPage(); await _commonTestCases.defaultSleepTime(); - await _dashboardPageRobot.dashboardMenuWidgetRobot.navigateToWalletMenu(); + await _selectWalletTypeForWallet(walletTypeToCreate); + await _commonTestCases.defaultSleepTime(); + + await _generateNewWalletDetails(); + + await _confirmPreSeedInfo(); + + await _confirmWalletDetails(); await _commonTestCases.defaultSleepTime(); } + //* ========== Handles restore wallet flow from wallet list/menu =============== Future restoreWalletFromWalletMenu(WalletType walletType, String walletSeed) async { + _tester.printToConsole('Restoring ${walletType.name} Wallet'); await _walletListPageRobot.navigateToRestoreWalletOptionsPage(); await _commonTestCases.defaultSleepTime(); @@ -155,25 +138,42 @@ class CommonTestFlows { await _commonTestCases.defaultSleepTime(); } - Future _welcomeToRestoreFromSeedsOrKeysPath(WalletType walletTypeToRestore) async { - // --------- Welcome Page --------------- - await _welcomePageRobot.navigateToRestoreWalletPage(); - - // ----------- Restore Options Page ----------- - // Route to restore from seeds or keys page to continue flow - await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); - + //* ========== Handles setting up pin code for wallet on first install =============== + Future setupPinCodeForWallet(List pin) async { // ----------- SetupPinCode Page ------------- // Confirm initial defaults - Widgets to be displayed etc await _setupPinCodeRobot.isSetupPinCodePage(); - await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, true); - await _setupPinCodeRobot.enterPinCode(CommonTestConstants.pin, false); + await _setupPinCodeRobot.enterPinCode(pin, true); + await _setupPinCodeRobot.enterPinCode(pin, false); await _setupPinCodeRobot.tapSuccessButton(); + } + + Future _welcomeToCreateWalletPath( + WalletType walletTypeToCreate, + List pin, + ) async { + await _welcomePageRobot.navigateToCreateNewWalletPage(); + + await setupPinCodeForWallet(pin); + + await _selectWalletTypeForWallet(walletTypeToCreate); + } + + Future _welcomeToRestoreFromSeedsOrKeysPath( + WalletType walletTypeToRestore, + List pin, + ) async { + await _welcomePageRobot.navigateToRestoreWalletPage(); + + await _restoreOptionsPageRobot.navigateToRestoreFromSeedsOrKeysPage(); + + await setupPinCodeForWallet(pin); await _selectWalletTypeForWallet(walletTypeToRestore); } + //* ============ Handles New Wallet Type Page ================== Future _selectWalletTypeForWallet(WalletType type) async { // ----------- NewWalletType Page ------------- // Confirm scroll behaviour works properly @@ -184,7 +184,38 @@ class CommonTestFlows { await _newWalletTypePageRobot.onNextButtonPressed(); } - // Main Restore Actions - On the RestoreFromSeed/Keys Page + //* ============ Handles New Wallet Page ================== + Future _generateNewWalletDetails() async { + await _newWalletPageRobot.isNewWalletPage(); + + await _newWalletPageRobot.generateWalletName(); + + await _newWalletPageRobot.onNextButtonPressed(); + } + + //* ============ Handles Pre Seed Page ===================== + Future _confirmPreSeedInfo() async { + await _preSeedPageRobot.isPreSeedPage(); + + await _preSeedPageRobot.onConfirmButtonPressed(); + } + + //* ============ Handles Wallet Seed Page ================== + Future _confirmWalletDetails() async { + await _walletSeedPageRobot.isWalletSeedPage(); + + _walletSeedPageRobot.confirmWalletDetailsDisplayCorrectly(); + + _walletSeedPageRobot.confirmWalletSeedReminderDisplays(); + + await _walletSeedPageRobot.onCopySeedsButtonPressed(); + + await _walletSeedPageRobot.onNextButtonPressed(); + + await _walletSeedPageRobot.onConfirmButtonOnSeedAlertDialogPressed(); + } + + //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action Future _restoreFromSeeds(String walletSeed) async { // ----------- RestoreFromSeedOrKeys Page ------------- @@ -193,6 +224,7 @@ class CommonTestFlows { await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); } + //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Keys Action Future _restoreFromKeys() async { await _commonTestCases.swipePage(); await _commonTestCases.defaultSleepTime(); @@ -204,4 +236,60 @@ class CommonTestFlows { await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(''); await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); } + + //* ====== Utility Function to get test wallet seeds for each wallet type ======== + String getWalletSeedsByWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.monero: + return secrets.moneroTestWalletSeeds; + case WalletType.bitcoin: + return secrets.bitcoinTestWalletSeeds; + case WalletType.ethereum: + return secrets.ethereumTestWalletSeeds; + case WalletType.litecoin: + return secrets.litecoinTestWalletSeeds; + case WalletType.bitcoinCash: + return secrets.bitcoinCashTestWalletSeeds; + case WalletType.polygon: + return secrets.polygonTestWalletSeeds; + case WalletType.solana: + return secrets.solanaTestWalletSeeds; + case WalletType.tron: + return secrets.tronTestWalletSeeds; + case WalletType.nano: + return secrets.nanoTestWalletSeeds; + case WalletType.wownero: + return secrets.wowneroTestWalletSeeds; + default: + return ''; + } + } + + //* ====== Utility Function to get test receive address for each wallet type ======== + String getReceiveAddressByWalletType(WalletType walletType) { + switch (walletType) { + case WalletType.monero: + return secrets.moneroTestWalletReceiveAddress; + case WalletType.bitcoin: + return secrets.bitcoinTestWalletReceiveAddress; + case WalletType.ethereum: + return secrets.ethereumTestWalletReceiveAddress; + case WalletType.litecoin: + return secrets.litecoinTestWalletReceiveAddress; + case WalletType.bitcoinCash: + return secrets.bitcoinCashTestWalletReceiveAddress; + case WalletType.polygon: + return secrets.polygonTestWalletReceiveAddress; + case WalletType.solana: + return secrets.solanaTestWalletReceiveAddress; + case WalletType.tron: + return secrets.tronTestWalletReceiveAddress; + case WalletType.nano: + return secrets.nanoTestWalletReceiveAddress; + case WalletType.wownero: + return secrets.wowneroTestWalletReceiveAddress; + default: + return ''; + } + } } diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart index caebe8b49e..026d94fb2c 100644 --- a/integration_test/funds_related_tests.dart +++ b/integration_test/funds_related_tests.dart @@ -36,6 +36,7 @@ void main() { await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( CommonTestConstants.testWalletType, secrets.solanaTestWalletSeeds, + CommonTestConstants.pin, ); // ----------- RestoreFromSeedOrKeys Page ------------- diff --git a/integration_test/robots/new_wallet_page_robot.dart b/integration_test/robots/new_wallet_page_robot.dart new file mode 100644 index 0000000000..f8deb00ae8 --- /dev/null +++ b/integration_test/robots/new_wallet_page_robot.dart @@ -0,0 +1,35 @@ +import 'package:cake_wallet/src/screens/new_wallet/new_wallet_page.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class NewWalletPageRobot { + NewWalletPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isNewWalletPage() async { + await commonTestCases.isSpecificPage(); + } + + Future enterWalletName(String walletName) async { + await commonTestCases.enterText( + walletName, + 'new_wallet_page_wallet_name_textformfield_key', + ); + await commonTestCases.defaultSleepTime(); + } + + Future generateWalletName() async { + await commonTestCases.tapItemByKey( + 'new_wallet_page_wallet_name_textformfield_generate_name_button_key', + ); + await commonTestCases.defaultSleepTime(); + } + + Future onNextButtonPressed() async { + await commonTestCases.tapItemByKey('new_wallet_page_confirm_button_key'); + await commonTestCases.defaultSleepTime(); + } +} diff --git a/integration_test/robots/pre_seed_page_robot.dart b/integration_test/robots/pre_seed_page_robot.dart new file mode 100644 index 0000000000..01be1249cb --- /dev/null +++ b/integration_test/robots/pre_seed_page_robot.dart @@ -0,0 +1,20 @@ +import 'package:cake_wallet/src/screens/seed/pre_seed_page.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class PreSeedPageRobot { + PreSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isPreSeedPage() async { + await commonTestCases.isSpecificPage(); + } + + Future onConfirmButtonPressed() async { + await commonTestCases.tapItemByKey('pre_seed_page_button_key'); + await commonTestCases.defaultSleepTime(); + } +} diff --git a/integration_test/robots/wallet_seed_page_robot.dart b/integration_test/robots/wallet_seed_page_robot.dart new file mode 100644 index 0000000000..d52f3b1ec9 --- /dev/null +++ b/integration_test/robots/wallet_seed_page_robot.dart @@ -0,0 +1,57 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/seed/wallet_seed_page.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class WalletSeedPageRobot { + WalletSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isWalletSeedPage() async { + await commonTestCases.isSpecificPage(); + } + + Future onNextButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_next_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future onConfirmButtonOnSeedAlertDialogPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_seed_alert_confirm_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future onBackButtonOnSeedAlertDialogPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_seed_alert_back_button_key'); + await commonTestCases.defaultSleepTime(); + } + + void confirmWalletDetailsDisplayCorrectly() { + final walletSeedPage = tester.widget(find.byType(WalletSeedPage)); + + final walletSeedViewModel = walletSeedPage.walletSeedViewModel; + + final walletName = walletSeedViewModel.name; + final walletSeeds = walletSeedViewModel.seed; + + commonTestCases.hasText(walletName); + commonTestCases.hasText(walletSeeds); + } + + void confirmWalletSeedReminderDisplays() { + commonTestCases.hasText(S.current.seed_reminder); + } + + Future onSaveSeedsButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_save_seeds_button_key'); + await commonTestCases.defaultSleepTime(); + } + + Future onCopySeedsButtonPressed() async { + await commonTestCases.tapItemByKey('wallet_seed_page_copy_seeds_button_key'); + await commonTestCases.defaultSleepTime(); + } +} diff --git a/integration_test/test_suites/create_wallet_flow_test.dart b/integration_test/test_suites/create_wallet_flow_test.dart new file mode 100644 index 0000000000..71bbab386c --- /dev/null +++ b/integration_test/test_suites/create_wallet_flow_test.dart @@ -0,0 +1,55 @@ +import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../components/common_test_constants.dart'; +import '../components/common_test_flows.dart'; +import '../robots/dashboard_page_robot.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + CommonTestFlows commonTestFlows; + DashboardPageRobot dashboardPageRobot; + + testWidgets( + 'Create Wallet Flow', + (tester) async { + commonTestFlows = CommonTestFlows(tester); + dashboardPageRobot = DashboardPageRobot(tester); + + // Start the app + await commonTestFlows.startAppFlow( + ValueKey('create_wallets_through_seeds_test_app_key'), + ); + + await commonTestFlows.welcomePageToCreateNewWalletFlow( + WalletType.solana, + CommonTestConstants.pin, + ); + + // Confirm it actually restores a solana wallet + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana); + + // Do the same for other available wallet types + for (var walletType in availableWalletTypes) { + if (walletType == WalletType.solana) { + continue; + } + + await commonTestFlows.switchToWalletMenuFromDashboardPage(); + + await commonTestFlows.createNewWalletFromWalletMenu(walletType); + + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType); + } + + // Go to the wallet menu, provides a visual confirmation that all the wallets were correctly restored + await commonTestFlows.switchToWalletMenuFromDashboardPage(); + + await Future.delayed(Duration(seconds: 5)); + }, + ); +} diff --git a/integration_test/test_suites/exchange_flow_test.dart b/integration_test/test_suites/exchange_flow_test.dart index 333506ef83..4f1b9b76e1 100644 --- a/integration_test/test_suites/exchange_flow_test.dart +++ b/integration_test/test_suites/exchange_flow_test.dart @@ -34,6 +34,7 @@ void main() { await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( CommonTestConstants.testWalletType, secrets.solanaTestWalletSeeds, + CommonTestConstants.pin, ); await dashboardPageRobot.navigateToExchangePage(); @@ -46,7 +47,7 @@ void main() { depositAddress: CommonTestConstants.testWalletAddress, ); await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); - + await exchangePageRobot.onExchangeButtonPressed(); await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount); diff --git a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart index bd1d1f07b0..365e413fd7 100644 --- a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart +++ b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart @@ -4,6 +4,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:integration_test/integration_test.dart'; +import '../components/common_test_constants.dart'; import '../components/common_test_flows.dart'; import '../robots/dashboard_page_robot.dart'; import 'package:cake_wallet/.secrets.g.dart' as secrets; @@ -29,6 +30,7 @@ void main() { await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( WalletType.solana, secrets.solanaTestWalletSeeds, + CommonTestConstants.pin, ); // Confirm it actually restores a solana wallet @@ -40,7 +42,9 @@ void main() { continue; } - await commonTestFlows.switchAndRestoreWalletFromDashboardPage( + await commonTestFlows.switchToWalletMenuFromDashboardPage(); + + await commonTestFlows.restoreWalletFromWalletMenu( walletType, commonTestFlows.getWalletSeedsByWalletType(walletType), ); diff --git a/integration_test/test_suites/send_flow_test.dart b/integration_test/test_suites/send_flow_test.dart index 4ad902b833..963ce35484 100644 --- a/integration_test/test_suites/send_flow_test.dart +++ b/integration_test/test_suites/send_flow_test.dart @@ -25,6 +25,7 @@ void main() { await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( CommonTestConstants.testWalletType, secrets.solanaTestWalletSeeds, + CommonTestConstants.pin, ); await dashboardPageRobot.navigateToSendPage(); diff --git a/lib/src/screens/InfoPage.dart b/lib/src/screens/InfoPage.dart index 5398df22c2..84b9e86327 100644 --- a/lib/src/screens/InfoPage.dart +++ b/lib/src/screens/InfoPage.dart @@ -21,6 +21,7 @@ abstract class InfoPage extends BasePage { String get pageTitle; String get pageDescription; String get buttonText; + Key? get buttonKey; void Function(BuildContext) get onPressed; @override @@ -39,15 +40,14 @@ abstract class InfoPage extends BasePage { alignment: Alignment.center, padding: EdgeInsets.all(24), child: ConstrainedBox( - constraints: BoxConstraints( - maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ Expanded( child: ConstrainedBox( - constraints: BoxConstraints( - maxHeight: MediaQuery.of(context).size.height * 0.3), + constraints: BoxConstraints(maxHeight: MediaQuery.of(context).size.height * 0.3), child: AspectRatio(aspectRatio: 1, child: image), ), ), @@ -61,14 +61,13 @@ abstract class InfoPage extends BasePage { height: 1.7, fontSize: 14, fontWeight: FontWeight.normal, - color: Theme.of(context) - .extension()! - .secondaryTextColor, + color: Theme.of(context).extension()!.secondaryTextColor, ), ), ), ), PrimaryButton( + key: buttonKey, onPressed: () => onPressed(context), text: buttonText, color: Theme.of(context).primaryColor, diff --git a/lib/src/screens/new_wallet/new_wallet_page.dart b/lib/src/screens/new_wallet/new_wallet_page.dart index b66aab4cf3..ad430c59cc 100644 --- a/lib/src/screens/new_wallet/new_wallet_page.dart +++ b/lib/src/screens/new_wallet/new_wallet_page.dart @@ -98,10 +98,13 @@ class _WalletNameFormState extends State { context: context, builder: (_) { return AlertWithOneAction( - alertTitle: S.current.new_wallet, - alertContent: state.error, - buttonText: S.of(context).ok, - buttonAction: () => Navigator.of(context).pop()); + key: ValueKey('new_wallet_page_failure_dialog_key'), + buttonKey: ValueKey('new_wallet_page_failure_dialog_button_key'), + alertTitle: S.current.new_wallet, + alertContent: state.error, + buttonText: S.of(context).ok, + buttonAction: () => Navigator.of(context).pop(), + ); }); } }); @@ -138,6 +141,7 @@ class _WalletNameFormState extends State { child: Column( children: [ TextFormField( + key: ValueKey('new_wallet_page_wallet_name_textformfield_key'), onChanged: (value) => _walletNewVM.name = value, controller: _nameController, textAlign: TextAlign.center, @@ -168,6 +172,7 @@ class _WalletNameFormState extends State { suffixIcon: Semantics( label: S.of(context).generate_name, child: IconButton( + key: ValueKey('new_wallet_page_wallet_name_textformfield_generate_name_button_key'), onPressed: () async { final rName = await generateName(); FocusManager.instance.primaryFocus?.unfocus(); @@ -283,6 +288,7 @@ class _WalletNameFormState extends State { builder: (BuildContext build) => Padding( padding: EdgeInsets.only(top: 24), child: SelectButton( + key: ValueKey('new_wallet_page_monero_seed_type_button_key'), text: widget._seedTypeViewModel.moneroSeedType.title, onTap: () async { await showPopUp( @@ -304,6 +310,7 @@ class _WalletNameFormState extends State { padding: EdgeInsets.only(top: 10), child: SeedLanguageSelector( key: _languageSelectorKey, + buttonKey: ValueKey('new_wallet_page_seed_language_selector_button_key'), initialSelected: defaultSeedLanguage, seedType: _walletNewVM.hasSeedType ? widget._seedTypeViewModel.moneroSeedType @@ -322,6 +329,7 @@ class _WalletNameFormState extends State { Observer( builder: (context) { return LoadingPrimaryButton( + key: ValueKey('new_wallet_page_confirm_button_key'), onPressed: _confirmForm, text: S.of(context).seed_language_next, color: Colors.green, @@ -333,6 +341,7 @@ class _WalletNameFormState extends State { ), const SizedBox(height: 25), GestureDetector( + key: ValueKey('new_wallet_page_advanced_settings_button_key'), onTap: () { Navigator.of(context).pushNamed(Routes.advancedPrivacySettings, arguments: { "type": _walletNewVM.type, diff --git a/lib/src/screens/seed/pre_seed_page.dart b/lib/src/screens/seed/pre_seed_page.dart index 730dfa5f80..23de4564fe 100644 --- a/lib/src/screens/seed/pre_seed_page.dart +++ b/lib/src/screens/seed/pre_seed_page.dart @@ -15,13 +15,15 @@ class PreSeedPage extends InfoPage { String get pageTitle => S.current.pre_seed_title; @override - String get pageDescription => - S.current.pre_seed_description(seedPhraseLength.toString()); + String get pageDescription => S.current.pre_seed_description(seedPhraseLength.toString()); @override String get buttonText => S.current.pre_seed_button_text; @override - void Function(BuildContext) get onPressed => (BuildContext context) => - Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true); + Key? get buttonKey => ValueKey('pre_seed_page_button_key'); + + @override + void Function(BuildContext) get onPressed => + (BuildContext context) => Navigator.of(context).popAndPushNamed(Routes.seed, arguments: true); } diff --git a/lib/src/screens/seed/wallet_seed_page.dart b/lib/src/screens/seed/wallet_seed_page.dart index 200b87b7d4..10160839cd 100644 --- a/lib/src/screens/seed/wallet_seed_page.dart +++ b/lib/src/screens/seed/wallet_seed_page.dart @@ -33,16 +33,22 @@ class WalletSeedPage extends BasePage { void onClose(BuildContext context) async { if (isNewWalletCreated) { final confirmed = await showPopUp( - context: context, - builder: (BuildContext context) { - return AlertWithTwoActions( - alertTitle: S.of(context).seed_alert_title, - alertContent: S.of(context).seed_alert_content, - leftButtonText: S.of(context).seed_alert_back, - rightButtonText: S.of(context).seed_alert_yes, - actionLeftButton: () => Navigator.of(context).pop(false), - actionRightButton: () => Navigator.of(context).pop(true)); - }) ?? + context: context, + builder: (BuildContext context) { + return AlertWithTwoActions( + alertDialogKey: ValueKey('wallet_seed_page_seed_alert_dialog_key'), + alertRightActionButtonKey: + ValueKey('wallet_seed_page_seed_alert_confirm_button_key'), + alertLeftActionButtonKey: ValueKey('wallet_seed_page_seed_alert_back_button_key'), + alertTitle: S.of(context).seed_alert_title, + alertContent: S.of(context).seed_alert_content, + leftButtonText: S.of(context).seed_alert_back, + rightButtonText: S.of(context).seed_alert_yes, + actionLeftButton: () => Navigator.of(context).pop(false), + actionRightButton: () => Navigator.of(context).pop(true), + ); + }, + ) ?? false; if (confirmed) { @@ -62,6 +68,7 @@ class WalletSeedPage extends BasePage { Widget trailing(BuildContext context) { return isNewWalletCreated ? GestureDetector( + key: ValueKey('wallet_seed_page_next_button_key'), onTap: () => onClose(context), child: Container( width: 100, @@ -74,9 +81,9 @@ class WalletSeedPage extends BasePage { child: Text( S.of(context).seed_language_next, style: TextStyle( - fontSize: 14, fontWeight: FontWeight.w600, color: Theme.of(context) - .extension()! - .buttonTextColor), + fontSize: 14, + fontWeight: FontWeight.w600, + color: Theme.of(context).extension()!.buttonTextColor), ), ), ) @@ -93,7 +100,8 @@ class WalletSeedPage extends BasePage { padding: EdgeInsets.all(24), alignment: Alignment.center, child: ConstrainedBox( - constraints: BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), + constraints: + BoxConstraints(maxWidth: ResponsiveLayoutUtilBase.kDesktopMaxWidthConstraint), child: Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ @@ -106,6 +114,7 @@ class WalletSeedPage extends BasePage { crossAxisAlignment: CrossAxisAlignment.center, children: [ Text( + key: ValueKey('wallet_seed_page_wallet_name_text_key'), walletSeedViewModel.name, style: TextStyle( fontSize: 20, @@ -115,12 +124,14 @@ class WalletSeedPage extends BasePage { Padding( padding: EdgeInsets.only(top: 20, left: 16, right: 16), child: Text( + key: ValueKey('wallet_seed_page_wallet_seed_text_key'), walletSeedViewModel.seed, textAlign: TextAlign.center, style: TextStyle( fontSize: 14, fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.secondaryTextColor), + color: + Theme.of(context).extension()!.secondaryTextColor), ), ) ], @@ -132,12 +143,18 @@ class WalletSeedPage extends BasePage { ? Padding( padding: EdgeInsets.only(bottom: 43, left: 43, right: 43), child: Text( + key: ValueKey( + 'wallet_seed_page_wallet_seed_reminder_text_key', + ), S.of(context).seed_reminder, textAlign: TextAlign.center, style: TextStyle( - fontSize: 12, - fontWeight: FontWeight.normal, - color: Theme.of(context).extension()!.detailsTitlesColor), + fontSize: 12, + fontWeight: FontWeight.normal, + color: Theme.of(context) + .extension()! + .detailsTitlesColor, + ), ), ) : Offstage(), @@ -145,9 +162,10 @@ class WalletSeedPage extends BasePage { mainAxisSize: MainAxisSize.max, children: [ Flexible( - child: Container( - padding: EdgeInsets.only(right: 8.0), - child: PrimaryButton( + child: Container( + padding: EdgeInsets.only(right: 8.0), + child: PrimaryButton( + key: ValueKey('wallet_seed_page_save_seeds_button_key'), onPressed: () { ShareUtil.share( text: walletSeedViewModel.seed, @@ -156,22 +174,29 @@ class WalletSeedPage extends BasePage { }, text: S.of(context).save, color: Colors.green, - textColor: Colors.white), - )), + textColor: Colors.white, + ), + ), + ), Flexible( - child: Container( - padding: EdgeInsets.only(left: 8.0), - child: Builder( + child: Container( + padding: EdgeInsets.only(left: 8.0), + child: Builder( builder: (context) => PrimaryButton( - onPressed: () { - ClipboardUtil.setSensitiveDataToClipboard( - ClipboardData(text: walletSeedViewModel.seed)); - showBar(context, S.of(context).copied_to_clipboard); - }, - text: S.of(context).copy, - color: Theme.of(context).extension()!.indicatorsColor, - textColor: Colors.white)), - )) + key: ValueKey('wallet_seed_page_copy_seeds_button_key'), + onPressed: () { + ClipboardUtil.setSensitiveDataToClipboard( + ClipboardData(text: walletSeedViewModel.seed), + ); + showBar(context, S.of(context).copied_to_clipboard); + }, + text: S.of(context).copy, + color: Theme.of(context).extension()!.indicatorsColor, + textColor: Colors.white, + ), + ), + ), + ) ], ) ], diff --git a/lib/src/screens/setup_2fa/setup_2fa_info_page.dart b/lib/src/screens/setup_2fa/setup_2fa_info_page.dart index ff61876659..8aa0ac3c93 100644 --- a/lib/src/screens/setup_2fa/setup_2fa_info_page.dart +++ b/lib/src/screens/setup_2fa/setup_2fa_info_page.dart @@ -4,7 +4,6 @@ import 'package:cake_wallet/src/screens/InfoPage.dart'; import 'package:flutter/cupertino.dart'; class Setup2FAInfoPage extends InfoPage { - @override String get pageTitle => S.current.pre_seed_title; @@ -15,6 +14,9 @@ class Setup2FAInfoPage extends InfoPage { String get buttonText => S.current.understand; @override - void Function(BuildContext) get onPressed => (BuildContext context) => - Navigator.of(context).popAndPushNamed(Routes.setup_2faPage); + Key? get buttonKey => ValueKey('setup_2fa_info_page_button_key'); + + @override + void Function(BuildContext) get onPressed => + (BuildContext context) => Navigator.of(context).popAndPushNamed(Routes.setup_2faPage); } diff --git a/lib/src/widgets/seed_language_selector.dart b/lib/src/widgets/seed_language_selector.dart index dde78c58c5..078705e1fb 100644 --- a/lib/src/widgets/seed_language_selector.dart +++ b/lib/src/widgets/seed_language_selector.dart @@ -6,12 +6,16 @@ import 'package:cake_wallet/utils/show_pop_up.dart'; import 'package:flutter/material.dart'; class SeedLanguageSelector extends StatefulWidget { - SeedLanguageSelector( - {Key? key, required this.initialSelected, this.seedType = SeedType.defaultSeedType}) - : super(key: key); + SeedLanguageSelector({ + required this.initialSelected, + this.seedType = SeedType.defaultSeedType, + this.buttonKey, + Key? key, + }) : super(key: key); final String initialSelected; final SeedType seedType; + final Key? buttonKey; @override SeedLanguageSelectorState createState() => SeedLanguageSelectorState(selected: initialSelected); @@ -25,6 +29,7 @@ class SeedLanguageSelectorState extends State { @override Widget build(BuildContext context) { return SelectButton( + key: widget.buttonKey, image: null, text: "${seedLanguages.firstWhere((e) => e.name == selected).nameLocalized} (${S.of(context).seed_language})", From e9a41a1f56fd1b2f9165730c59245c78b0692443 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 13 Aug 2024 18:35:59 +0100 Subject: [PATCH 55/69] tests: Further optimize common flows --- .../components/common_test_flows.dart | 20 +++++++------------ ...estore_wallet_through_seeds_flow_test.dart | 6 ++++-- 2 files changed, 11 insertions(+), 15 deletions(-) diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index a35543fcba..1262d19455 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -96,7 +96,7 @@ class CommonTestFlows { Future startAppFlow(Key key) async { await app.main(topLevelKey: ValueKey('send_flow_test_app_key')); - + await _tester.pumpAndSettle(); // --------- Disclaimer Page ------------ @@ -122,18 +122,8 @@ class CommonTestFlows { await _restoreFromKeys(); } - Future switchAndRestoreWalletFromDashboardPage( - WalletType walletType, - String walletSeed, - ) async { - _tester.printToConsole('Switching to Wallet Menu'); - await switchToWalletMenuFromDashboardPage(); - - _tester.printToConsole('Restoring ${walletType.name} Wallet'); - await restoreWalletFromWalletMenu(walletType, walletSeed); - } - Future switchToWalletMenuFromDashboardPage() async { + _tester.printToConsole('Switching to Wallet Menu'); await _dashboardPageRobot.openDrawerMenu(); await _commonTestCases.defaultSleepTime(); @@ -141,7 +131,11 @@ class CommonTestFlows { await _commonTestCases.defaultSleepTime(); } - Future restoreWalletFromWalletMenu(WalletType walletType, String walletSeed) async { + Future restoreWalletFromWalletMenu( + WalletType walletType, + String walletSeed, + ) async { + _tester.printToConsole('Restoring ${walletType.name} Wallet'); await _walletListPageRobot.navigateToRestoreWalletOptionsPage(); await _commonTestCases.defaultSleepTime(); diff --git a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart index bd1d1f07b0..9cd711c726 100644 --- a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart +++ b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart @@ -36,11 +36,13 @@ void main() { // Do the same for other available wallet types for (var walletType in availableWalletTypes) { - if (walletType == WalletType.solana || walletType == WalletType.wownero) { + if (walletType == WalletType.solana) { continue; } - await commonTestFlows.switchAndRestoreWalletFromDashboardPage( + await commonTestFlows.switchToWalletMenuFromDashboardPage(); + + commonTestFlows.restoreWalletFromWalletMenu( walletType, commonTestFlows.getWalletSeedsByWalletType(walletType), ); From 7c169fe6e4f00e7a6b55303da3cb6028fa2ef0aa Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 13 Aug 2024 18:38:33 +0100 Subject: [PATCH 56/69] tests: Add missing await for call --- .../test_suites/restore_wallet_through_seeds_flow_test.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart index 9cd711c726..240e0e2846 100644 --- a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart +++ b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart @@ -42,7 +42,7 @@ void main() { await commonTestFlows.switchToWalletMenuFromDashboardPage(); - commonTestFlows.restoreWalletFromWalletMenu( + await commonTestFlows.restoreWalletFromWalletMenu( walletType, commonTestFlows.getWalletSeedsByWalletType(walletType), ); From 4555b0bd94fba61e62e6715f94686a01306d2004 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 16 Aug 2024 00:13:05 +0100 Subject: [PATCH 57/69] tests: Confirm Seeds Display Properly WIP --- .../settings/security_backup_page.dart | 50 +++-- .../widgets/settings_cell_with_arrow.dart | 7 +- .../widgets/settings_picker_cell.dart | 24 +-- .../widgets/settings_switcher_cell.dart | 3 +- .../standart_list_item.dart | 7 +- .../transaction_details_list_item.dart | 7 +- .../screens/wallet_keys/wallet_keys_page.dart | 1 + lib/src/widgets/standard_list.dart | 8 +- lib/view_model/wallet_keys_view_model.dart | 179 ++++++++++++------ 9 files changed, 187 insertions(+), 99 deletions(-) diff --git a/lib/src/screens/settings/security_backup_page.dart b/lib/src/screens/settings/security_backup_page.dart index 04ae53d77b..bb1c00ff50 100644 --- a/lib/src/screens/settings/security_backup_page.dart +++ b/lib/src/screens/settings/security_backup_page.dart @@ -16,7 +16,8 @@ import 'package:flutter/material.dart'; import 'package:flutter_mobx/flutter_mobx.dart'; class SecurityBackupPage extends BasePage { - SecurityBackupPage(this._securitySettingsViewModel, this._authService, [this._isHardwareWallet = false]); + SecurityBackupPage(this._securitySettingsViewModel, this._authService, + [this._isHardwareWallet = false]); final AuthService _authService; @@ -30,10 +31,13 @@ class SecurityBackupPage extends BasePage { @override Widget body(BuildContext context) { return Container( - padding: EdgeInsets.only(top: 10), - child: Column(mainAxisSize: MainAxisSize.min, children: [ + padding: EdgeInsets.only(top: 10), + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ if (!_isHardwareWallet) SettingsCellWithArrow( + key: ValueKey('security_backup_page_show_keys_button_key'), title: S.current.show_keys, handler: (_) => _authService.authenticateAction( context, @@ -44,15 +48,17 @@ class SecurityBackupPage extends BasePage { ), if (!SettingsStoreBase.walletPasswordDirectInput) SettingsCellWithArrow( + key: ValueKey('security_backup_page_create_backup_button_key'), title: S.current.create_backup, handler: (_) => _authService.authenticateAction( context, route: Routes.backup, - conditionToDetermineIfToUse2FA: _securitySettingsViewModel - .shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + conditionToDetermineIfToUse2FA: + _securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, ), ), SettingsCellWithArrow( + key: ValueKey('security_backup_page_change_pin_button_key'), title: S.current.settings_change_pin, handler: (_) => _authService.authenticateAction( context, @@ -60,28 +66,30 @@ class SecurityBackupPage extends BasePage { arguments: (PinCodeState setupPinContext, String _) { setupPinContext.close(); }, - conditionToDetermineIfToUse2FA: _securitySettingsViewModel - .shouldRequireTOTP2FAForAllSecurityAndBackupSettings, + conditionToDetermineIfToUse2FA: + _securitySettingsViewModel.shouldRequireTOTP2FAForAllSecurityAndBackupSettings, ), ), if (DeviceInfo.instance.isMobile || Platform.isMacOS || Platform.isLinux) Observer(builder: (_) { return SettingsSwitcherCell( + key: ValueKey('security_backup_page_allow_biometrics_button_key'), title: S.current.settings_allow_biometrical_authentication, value: _securitySettingsViewModel.allowBiometricalAuthentication, onValueChange: (BuildContext context, bool value) { if (value) { - _authService.authenticateAction(context, - onAuthSuccess: (isAuthenticatedSuccessfully) async { - if (isAuthenticatedSuccessfully) { - if (await _securitySettingsViewModel.biometricAuthenticated()) { + _authService.authenticateAction( + context, + onAuthSuccess: (isAuthenticatedSuccessfully) async { + if (isAuthenticatedSuccessfully) { + if (await _securitySettingsViewModel.biometricAuthenticated()) { + _securitySettingsViewModel + .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); + } + } else { _securitySettingsViewModel .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); } - } else { - _securitySettingsViewModel - .setAllowBiometricalAuthentication(isAuthenticatedSuccessfully); - } }, conditionToDetermineIfToUse2FA: _securitySettingsViewModel .shouldRequireTOTP2FAForAllSecurityAndBackupSettings, @@ -93,6 +101,7 @@ class SecurityBackupPage extends BasePage { }), Observer(builder: (_) { return SettingsPickerCell( + key: ValueKey('security_backup_page_require_pin_after_button_key'), title: S.current.require_pin_after, items: PinCodeRequiredDuration.values, selectedItem: _securitySettingsViewModel.pinCodeRequiredDuration, @@ -104,14 +113,15 @@ class SecurityBackupPage extends BasePage { Observer( builder: (context) { return SettingsCellWithArrow( + key: ValueKey('security_backup_page_totp_2fa_button_key'), title: _securitySettingsViewModel.useTotp2FA ? S.current.modify_2fa : S.current.setup_2fa, - handler: (_) => _authService.authenticateAction( - context, - route: _securitySettingsViewModel.useTotp2FA - ? Routes.modify2FAPage - : Routes.setup2faInfoPage, + handler: (_) => _authService.authenticateAction( + context, + route: _securitySettingsViewModel.useTotp2FA + ? Routes.modify2FAPage + : Routes.setup2faInfoPage, conditionToDetermineIfToUse2FA: _securitySettingsViewModel .shouldRequireTOTP2FAForAllSecurityAndBackupSettings, ), diff --git a/lib/src/screens/settings/widgets/settings_cell_with_arrow.dart b/lib/src/screens/settings/widgets/settings_cell_with_arrow.dart index f0e19a7156..cb4f9dc781 100644 --- a/lib/src/screens/settings/widgets/settings_cell_with_arrow.dart +++ b/lib/src/screens/settings/widgets/settings_cell_with_arrow.dart @@ -3,8 +3,11 @@ import 'package:cake_wallet/src/widgets/standard_list.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; class SettingsCellWithArrow extends StandardListRow { - SettingsCellWithArrow({required String title, required Function(BuildContext context)? handler}) - : super(title: title, isSelected: false, onTap: handler); + SettingsCellWithArrow({ + required String title, + required Function(BuildContext context)? handler, + Key? key, + }) : super(title: title, isSelected: false, onTap: handler, key: key); @override Widget buildTrailing(BuildContext context) => Image.asset('assets/images/select_arrow.png', diff --git a/lib/src/screens/settings/widgets/settings_picker_cell.dart b/lib/src/screens/settings/widgets/settings_picker_cell.dart index 8e04923305..765ac2991f 100644 --- a/lib/src/screens/settings/widgets/settings_picker_cell.dart +++ b/lib/src/screens/settings/widgets/settings_picker_cell.dart @@ -5,19 +5,21 @@ import 'package:cake_wallet/src/widgets/picker.dart'; import 'package:cake_wallet/src/widgets/standard_list.dart'; class SettingsPickerCell extends StandardListRow { - SettingsPickerCell( - {required String title, - required this.selectedItem, - required this.items, - this.displayItem, - this.images, - this.searchHintText, - this.isGridView = false, - this.matchingCriteria, - this.onItemSelected}) - : super( + SettingsPickerCell({ + required String title, + required this.selectedItem, + required this.items, + this.displayItem, + this.images, + this.searchHintText, + this.isGridView = false, + this.matchingCriteria, + this.onItemSelected, + Key? key, + }) : super( title: title, isSelected: false, + key: key, onTap: (BuildContext context) async { final selectedAtIndex = items.indexOf(selectedItem); diff --git a/lib/src/screens/settings/widgets/settings_switcher_cell.dart b/lib/src/screens/settings/widgets/settings_switcher_cell.dart index 0e5c04524e..6a3c8b4a0c 100644 --- a/lib/src/screens/settings/widgets/settings_switcher_cell.dart +++ b/lib/src/screens/settings/widgets/settings_switcher_cell.dart @@ -10,7 +10,8 @@ class SettingsSwitcherCell extends StandardListRow { Decoration? decoration, this.leading, void Function(BuildContext context)? onTap, - }) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap); + Key? key, + }) : super(title: title, isSelected: false, decoration: decoration, onTap: onTap, key: key); final bool value; final void Function(BuildContext context, bool value)? onValueChange; diff --git a/lib/src/screens/transaction_details/standart_list_item.dart b/lib/src/screens/transaction_details/standart_list_item.dart index 705daf9702..673eabe424 100644 --- a/lib/src/screens/transaction_details/standart_list_item.dart +++ b/lib/src/screens/transaction_details/standart_list_item.dart @@ -1,6 +1,9 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; class StandartListItem extends TransactionDetailsListItem { - StandartListItem({required String title, required String value}) - : super(title: title, value: value); + StandartListItem({ + required String super.title, + required String super.value, + super.key, + }); } diff --git a/lib/src/screens/transaction_details/transaction_details_list_item.dart b/lib/src/screens/transaction_details/transaction_details_list_item.dart index 8a84493507..446383d721 100644 --- a/lib/src/screens/transaction_details/transaction_details_list_item.dart +++ b/lib/src/screens/transaction_details/transaction_details_list_item.dart @@ -1,6 +1,9 @@ +import 'package:flutter/foundation.dart'; + abstract class TransactionDetailsListItem { - TransactionDetailsListItem({required this.title, required this.value}); + TransactionDetailsListItem({required this.title, required this.value, this.key}); final String title; final String value; -} \ No newline at end of file + final Key? key; +} diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index 5117f152fa..e2cd2f5c10 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -60,6 +60,7 @@ class WalletKeysPage extends BasePage { child: Padding( padding: const EdgeInsets.all(8.0), child: AutoSizeText( + key: ValueKey('wallet_keys_page_share_warning_text_key'), S.of(context).do_not_share_warning_text.toUpperCase(), textAlign: TextAlign.center, maxLines: 4, diff --git a/lib/src/widgets/standard_list.dart b/lib/src/widgets/standard_list.dart index c1fcae0524..0780d64cde 100644 --- a/lib/src/widgets/standard_list.dart +++ b/lib/src/widgets/standard_list.dart @@ -4,7 +4,13 @@ import 'package:cake_wallet/src/widgets/standard_list_status_row.dart'; import 'package:flutter/material.dart'; class StandardListRow extends StatelessWidget { - StandardListRow({required this.title, required this.isSelected, this.onTap, this.decoration}); + StandardListRow({ + required this.title, + required this.isSelected, + this.onTap, + this.decoration, + super.key, + }); final String title; final bool isSelected; diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 1d5c27fed2..479b633b18 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -10,6 +10,7 @@ import 'package:cw_core/transaction_info.dart'; import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:cw_monero/monero_wallet.dart'; +import 'package:flutter/foundation.dart'; import 'package:mobx/mobx.dart'; import 'package:polyseed/polyseed.dart'; @@ -24,6 +25,7 @@ abstract class WalletKeysViewModelBase with Store { _appStore.wallet!.type == WalletType.bitcoinCash ? S.current.wallet_seed : S.current.wallet_keys, + _walletName = _appStore.wallet!.type.name, _restoreHeight = _appStore.wallet!.walletInfo.restoreHeight, _restoreHeightByTransactions = 0, items = ObservableList() { @@ -38,12 +40,10 @@ abstract class WalletKeysViewModelBase with Store { _appStore.wallet!.type == WalletType.wownero) { final accountTransactions = _getWalletTransactions(_appStore.wallet!); if (accountTransactions.isNotEmpty) { - final incomingAccountTransactions = accountTransactions - .where((tx) => tx.direction == TransactionDirection.incoming); + final incomingAccountTransactions = + accountTransactions.where((tx) => tx.direction == TransactionDirection.incoming); if (incomingAccountTransactions.isNotEmpty) { - incomingAccountTransactions - .toList() - .sort((a, b) => a.date.compareTo(b.date)); + incomingAccountTransactions.toList().sort((a, b) => a.date.compareTo(b.date)); _restoreHeightByTransactions = _getRestoreHeightByTransactions( _appStore.wallet!.type, incomingAccountTransactions.first.date); } @@ -55,6 +55,8 @@ abstract class WalletKeysViewModelBase with Store { final String title; + final String _walletName; + final AppStore _appStore; final int _restoreHeight; @@ -70,37 +72,56 @@ abstract class WalletKeysViewModelBase with Store { items.addAll([ if (keys['publicSpendKey'] != null) StandartListItem( - title: S.current.spend_key_public, - value: keys['publicSpendKey']!), + key: ValueKey('${_walletName}_wallet_public_spend_key_item_key'), + title: S.current.spend_key_public, + value: keys['publicSpendKey']!, + ), if (keys['privateSpendKey'] != null) StandartListItem( - title: S.current.spend_key_private, - value: keys['privateSpendKey']!), + key: ValueKey('${_walletName}_wallet_private_spend_key_item_key'), + title: S.current.spend_key_private, + value: keys['privateSpendKey']!, + ), if (keys['publicViewKey'] != null) StandartListItem( - title: S.current.view_key_public, value: keys['publicViewKey']!), + key: ValueKey('${_walletName}_wallet_public_view_key_item_key'), + title: S.current.view_key_public, + value: keys['publicViewKey']!, + ), if (keys['privateViewKey'] != null) StandartListItem( - title: S.current.view_key_private, - value: keys['privateViewKey']!), + key: ValueKey('${_walletName}_wallet_private_view_key_item_key'), + title: S.current.view_key_private, + value: keys['privateViewKey']!, + ), if (_appStore.wallet!.seed!.isNotEmpty) - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + StandartListItem( + key: ValueKey('${_walletName}_wallet_seed_item_key'), + title: S.current.wallet_seed, + value: _appStore.wallet!.seed!, + ), ]); - if (_appStore.wallet?.seed != null && - Polyseed.isValidSeed(_appStore.wallet!.seed!)) { + if (_appStore.wallet?.seed != null && Polyseed.isValidSeed(_appStore.wallet!.seed!)) { final lang = PolyseedLang.getByPhrase(_appStore.wallet!.seed!); - items.add(StandartListItem( + items.add( + StandartListItem( + key: ValueKey('${_walletName}_wallet_seed_legacy_item_key'), title: S.current.wallet_seed_legacy, - value: (_appStore.wallet as MoneroWalletBase) - .seedLegacy(lang.nameEnglish))); + value: (_appStore.wallet as MoneroWalletBase).seedLegacy(lang.nameEnglish), + ), + ); } final restoreHeight = monero!.getRestoreHeight(_appStore.wallet!); if (restoreHeight != null) { - items.add(StandartListItem( + items.add( + StandartListItem( + key: ValueKey('${_walletName}_wallet_restore_height_item_key'), title: S.current.wallet_recovery_height, - value: restoreHeight.toString())); + value: restoreHeight.toString(), + ), + ); } } @@ -110,21 +131,34 @@ abstract class WalletKeysViewModelBase with Store { items.addAll([ if (keys['publicSpendKey'] != null) StandartListItem( - title: S.current.spend_key_public, - value: keys['publicSpendKey']!), + key: ValueKey('${_walletName}_wallet_public_spend_key_item_key'), + title: S.current.spend_key_public, + value: keys['publicSpendKey']!, + ), if (keys['privateSpendKey'] != null) StandartListItem( - title: S.current.spend_key_private, - value: keys['privateSpendKey']!), + key: ValueKey('${_walletName}_wallet_private_spend_key_item_key'), + title: S.current.spend_key_private, + value: keys['privateSpendKey']!, + ), if (keys['publicViewKey'] != null) StandartListItem( - title: S.current.view_key_public, value: keys['publicViewKey']!), + key: ValueKey('${_walletName}_wallet_public_view_key_item_key'), + title: S.current.view_key_public, + value: keys['publicViewKey']!, + ), if (keys['privateViewKey'] != null) StandartListItem( - title: S.current.view_key_private, - value: keys['privateViewKey']!), + key: ValueKey('${_walletName}_wallet_private_view_key_item_key'), + title: S.current.view_key_private, + value: keys['privateViewKey']!, + ), if (_appStore.wallet!.seed!.isNotEmpty) - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + StandartListItem( + key: ValueKey('${_walletName}_wallet_seed_item_key'), + title: S.current.wallet_seed, + value: _appStore.wallet!.seed!, + ), ]); } @@ -134,29 +168,45 @@ abstract class WalletKeysViewModelBase with Store { items.addAll([ if (keys['publicSpendKey'] != null) StandartListItem( - title: S.current.spend_key_public, - value: keys['publicSpendKey']!), + key: ValueKey('${_walletName}_wallet_public_spend_key_item_key'), + title: S.current.spend_key_public, + value: keys['publicSpendKey']!, + ), if (keys['privateSpendKey'] != null) StandartListItem( - title: S.current.spend_key_private, - value: keys['privateSpendKey']!), + key: ValueKey('${_walletName}_wallet_private_spend_key_item_key'), + title: S.current.spend_key_private, + value: keys['privateSpendKey']!, + ), if (keys['publicViewKey'] != null) StandartListItem( - title: S.current.view_key_public, value: keys['publicViewKey']!), + key: ValueKey('${_walletName}_wallet_public_view_key_item_key'), + title: S.current.view_key_public, + value: keys['publicViewKey']!, + ), if (keys['privateViewKey'] != null) StandartListItem( - title: S.current.view_key_private, - value: keys['privateViewKey']!), + key: ValueKey('${_walletName}_wallet_private_view_key_item_key'), + title: S.current.view_key_private, + value: keys['privateViewKey']!, + ), if (_appStore.wallet!.seed!.isNotEmpty) - StandartListItem(title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + StandartListItem( + key: ValueKey('${_walletName}_wallet_seed_item_key'), + title: S.current.wallet_seed, + value: _appStore.wallet!.seed!, + ), ]); - if (_appStore.wallet?.seed != null && - Polyseed.isValidSeed(_appStore.wallet!.seed!)) { + if (_appStore.wallet?.seed != null && Polyseed.isValidSeed(_appStore.wallet!.seed!)) { final lang = PolyseedLang.getByPhrase(_appStore.wallet!.seed!); - items.add(StandartListItem( + items.add( + StandartListItem( + key: ValueKey('${_walletName}_wallet_seed_legacy_item_key'), title: S.current.wallet_seed_legacy, - value: wownero!.getLegacySeed(_appStore.wallet!, lang.nameEnglish))); + value: wownero!.getLegacySeed(_appStore.wallet!, lang.nameEnglish), + ), + ); } } @@ -173,7 +223,10 @@ abstract class WalletKeysViewModelBase with Store { // if (keys['publicKey'] != null) // StandartListItem(title: S.current.public_key, value: keys['publicKey']!), StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + key: ValueKey('${_walletName}_wallet_seed_item_key'), + title: S.current.wallet_seed, + value: _appStore.wallet!.seed!, + ), ]); } @@ -183,31 +236,43 @@ abstract class WalletKeysViewModelBase with Store { items.addAll([ if (_appStore.wallet!.privateKey != null) StandartListItem( - title: S.current.private_key, - value: _appStore.wallet!.privateKey!), + key: ValueKey('${_walletName}_wallet_private_key_item_key'), + title: S.current.private_key, + value: _appStore.wallet!.privateKey!, + ), if (_appStore.wallet!.seed != null) StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + key: ValueKey('${_walletName}_wallet_seed_item_key'), + title: S.current.wallet_seed, + value: _appStore.wallet!.seed!, + ), ]); } - bool nanoBased = _appStore.wallet!.type == WalletType.nano || - _appStore.wallet!.type == WalletType.banano; + bool nanoBased = + _appStore.wallet!.type == WalletType.nano || _appStore.wallet!.type == WalletType.banano; if (nanoBased) { // we always have the hex version of the seed and private key: items.addAll([ if (_appStore.wallet!.seed != null) StandartListItem( - title: S.current.wallet_seed, value: _appStore.wallet!.seed!), + key: ValueKey('${_walletName}_wallet_seed_item_key'), + title: S.current.wallet_seed, + value: _appStore.wallet!.seed!, + ), if (_appStore.wallet!.hexSeed != null) StandartListItem( - title: S.current.seed_hex_form, - value: _appStore.wallet!.hexSeed!), + key: ValueKey('${_walletName}_wallet_hex_seed_key'), + title: S.current.seed_hex_form, + value: _appStore.wallet!.hexSeed!, + ), if (_appStore.wallet!.privateKey != null) StandartListItem( - title: S.current.private_key, - value: _appStore.wallet!.privateKey!), + key: ValueKey('${_walletName}_wallet_private_key_item_key'), + title: S.current.private_key, + value: _appStore.wallet!.privateKey!, + ), ]); } } @@ -273,8 +338,7 @@ abstract class WalletKeysViewModelBase with Store { if (_appStore.wallet!.seed != null) 'seed': _appStore.wallet!.seed!, if (_appStore.wallet!.seed == null && _appStore.wallet!.hexSeed != null) 'hexSeed': _appStore.wallet!.hexSeed!, - if (_appStore.wallet!.seed == null && - _appStore.wallet!.privateKey != null) + if (_appStore.wallet!.seed == null && _appStore.wallet!.privateKey != null) 'private_key': _appStore.wallet!.privateKey!, if (restoreHeightResult != null) ...{'height': restoreHeightResult} }; @@ -291,11 +355,7 @@ abstract class WalletKeysViewModelBase with Store { } else if (wallet.type == WalletType.haven) { return haven!.getTransactionHistory(wallet).transactions.values.toList(); } else if (wallet.type == WalletType.wownero) { - return wownero! - .getTransactionHistory(wallet) - .transactions - .values - .toList(); + return wownero!.getTransactionHistory(wallet).transactions.values.toList(); } return []; } @@ -311,6 +371,5 @@ abstract class WalletKeysViewModelBase with Store { return 0; } - String getRoundedRestoreHeight(int height) => - ((height / 1000).floor() * 1000).toString(); + String getRoundedRestoreHeight(int height) => ((height / 1000).floor() * 1000).toString(); } From d197b7150a7ad541fe85392a73e7cb673245ecaa Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Mon, 19 Aug 2024 16:27:35 +0100 Subject: [PATCH 58/69] tests: Confirm Seeds Display Correctly Automated Tests --- .../components/common_test_cases.dart | 5 + .../components/common_test_flows.dart | 2 - .../robots/dashboard_menu_widget_robot.dart | 7 + .../robots/dashboard_page_robot.dart | 1 + .../security_and_backup_page_robot.dart | 24 +++ .../robots/wallet_keys_robot.dart | 162 ++++++++++++++++++ .../test_suites/confirm_seeds_flow_test.dart | 102 +++++++++++ ios/Podfile.lock | 55 +----- .../screens/wallet_keys/wallet_keys_page.dart | 35 ++-- lib/view_model/wallet_keys_view_model.dart | 2 + 10 files changed, 323 insertions(+), 72 deletions(-) create mode 100644 integration_test/robots/security_and_backup_page_robot.dart create mode 100644 integration_test/robots/wallet_keys_robot.dart create mode 100644 integration_test/test_suites/confirm_seeds_flow_test.dart diff --git a/integration_test/components/common_test_cases.dart b/integration_test/components/common_test_cases.dart index 2e2991804b..7ae1c00f37 100644 --- a/integration_test/components/common_test_cases.dart +++ b/integration_test/components/common_test_cases.dart @@ -31,6 +31,11 @@ class CommonTestCases { expect(typeWidget, findsOneWidget); } + bool isKeyPresent(String key) { + final typeWidget = find.byKey(ValueKey(key)); + return typeWidget.tryEvaluate(); + } + void hasValueKey(String key) { final typeWidget = find.byKey(ValueKey(key)); expect(typeWidget, findsOneWidget); diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index 14eacf7e6a..e523d6c0b1 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -99,10 +99,8 @@ class CommonTestFlows { Future switchToWalletMenuFromDashboardPage() async { _tester.printToConsole('Switching to Wallet Menu'); await _dashboardPageRobot.openDrawerMenu(); - await _commonTestCases.defaultSleepTime(); await _dashboardPageRobot.dashboardMenuWidgetRobot.navigateToWalletMenu(); - await _commonTestCases.defaultSleepTime(); } //* ========== Handles creating new wallet flow from wallet list/menu =============== diff --git a/integration_test/robots/dashboard_menu_widget_robot.dart b/integration_test/robots/dashboard_menu_widget_robot.dart index 830f436b4d..f48033dda7 100644 --- a/integration_test/robots/dashboard_menu_widget_robot.dart +++ b/integration_test/robots/dashboard_menu_widget_robot.dart @@ -29,4 +29,11 @@ class DashboardMenuWidgetRobot { await commonTestCases.tapItemByKey('dashboard_page_menu_widget_wallet_menu_button_key'); await commonTestCases.defaultSleepTime(); } + + Future navigateToSecurityAndBackupPage() async { + await commonTestCases.tapItemByKey( + 'dashboard_page_menu_widget_security_and_backup_button_key', + ); + await commonTestCases.defaultSleepTime(); + } } diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart index 81f6514a5b..9d532a7f4f 100644 --- a/integration_test/robots/dashboard_page_robot.dart +++ b/integration_test/robots/dashboard_page_robot.dart @@ -77,6 +77,7 @@ class DashboardPageRobot { Future openDrawerMenu() async { await commonTestCases.tapItemByKey('dashboard_page_wallet_menu_button_key'); + await commonTestCases.defaultSleepTime(); } Future navigateToBuyPage() async { diff --git a/integration_test/robots/security_and_backup_page_robot.dart b/integration_test/robots/security_and_backup_page_robot.dart new file mode 100644 index 0000000000..eb7c1bc876 --- /dev/null +++ b/integration_test/robots/security_and_backup_page_robot.dart @@ -0,0 +1,24 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/settings/security_backup_page.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class SecurityAndBackupPageRobot { + SecurityAndBackupPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + final CommonTestCases commonTestCases; + + Future isSecurityAndBackupPage() async { + await commonTestCases.isSpecificPage(); + } + + void hasTitle() { + commonTestCases.hasText(S.current.security_and_backup); + } + + Future navigateToShowKeysPage() async { + await commonTestCases.tapItemByKey('security_backup_page_show_keys_button_key'); + } +} diff --git a/integration_test/robots/wallet_keys_robot.dart b/integration_test/robots/wallet_keys_robot.dart new file mode 100644 index 0000000000..f6aeb3a66f --- /dev/null +++ b/integration_test/robots/wallet_keys_robot.dart @@ -0,0 +1,162 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/reactions/wallet_connect.dart'; +import 'package:cake_wallet/src/screens/wallet_keys/wallet_keys_page.dart'; +import 'package:cake_wallet/store/app_store.dart'; +import 'package:cw_core/monero_wallet_keys.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:cw_monero/monero_wallet.dart'; +import 'package:cw_wownero/wownero_wallet.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:polyseed/polyseed.dart'; + +import '../components/common_test_cases.dart'; + +class WalletKeysAndSeedPageRobot { + WalletKeysAndSeedPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + final CommonTestCases commonTestCases; + + Future isWalletKeysAndSeedPage() async { + await commonTestCases.isSpecificPage(); + } + + void hasTitle() { + final walletKeysPage = tester.widget(find.byType(WalletKeysPage)); + final walletKeysViewModel = walletKeysPage.walletKeysViewModel; + commonTestCases.hasText(walletKeysViewModel.title); + } + + void hasShareWarning() { + commonTestCases.hasText(S.current.do_not_share_warning_text.toUpperCase()); + } + + Future confirmWalletCredentials(WalletType walletType) async { + final walletKeysPage = tester.widget(find.byType(WalletKeysPage)); + final walletKeysViewModel = walletKeysPage.walletKeysViewModel; + + final appStore = walletKeysViewModel.appStore; + final walletName = walletType.name; + bool hasSeed = appStore.wallet!.seed != null; + bool hasHexSeed = appStore.wallet!.hexSeed != null; + bool hasPrivateKey = appStore.wallet!.privateKey != null; + + if (walletType == WalletType.monero) { + final moneroWallet = appStore.wallet as MoneroWallet; + final lang = PolyseedLang.getByPhrase(moneroWallet.seed); + final legacySeed = moneroWallet.seedLegacy(lang.nameEnglish); + + _confirmMoneroWalletCredentials( + appStore, + walletName, + moneroWallet.seed, + legacySeed, + ); + } + + if (walletType == WalletType.wownero) { + final wowneroWallet = appStore.wallet as WowneroWallet; + final lang = PolyseedLang.getByPhrase(wowneroWallet.seed); + final legacySeed = wowneroWallet.seedLegacy(lang.nameEnglish); + + _confirmMoneroWalletCredentials( + appStore, + walletName, + wowneroWallet.seed, + legacySeed, + ); + } + + if (walletType == WalletType.bitcoin || + walletType == WalletType.litecoin || + walletType == WalletType.bitcoinCash) { + commonTestCases.hasText(appStore.wallet!.seed!); + tester.printToConsole('$walletName wallet has seeds properly displayed'); + } + + if (isEVMCompatibleChain(walletType) || + walletType == WalletType.solana || + walletType == WalletType.tron) { + if (hasSeed) { + commonTestCases.hasText(appStore.wallet!.seed!); + tester.printToConsole('$walletName wallet has seeds properly displayed'); + } + if (hasPrivateKey) { + commonTestCases.hasText(appStore.wallet!.privateKey!); + tester.printToConsole('$walletName wallet has private key properly displayed'); + } + } + + if (walletType == WalletType.nano || walletType == WalletType.banano) { + if (hasSeed) { + commonTestCases.hasText(appStore.wallet!.seed!); + tester.printToConsole('$walletName wallet has seeds properly displayed'); + } + if (hasHexSeed) { + commonTestCases.hasText(appStore.wallet!.hexSeed!); + tester.printToConsole('$walletName wallet has hexSeed properly displayed'); + } + if (hasPrivateKey) { + commonTestCases.hasText(appStore.wallet!.privateKey!); + tester.printToConsole('$walletName wallet has private key properly displayed'); + } + } + + await commonTestCases.defaultSleepTime(seconds: 5); + } + + void _confirmMoneroWalletCredentials( + AppStore appStore, + String walletName, + String seed, + String legacySeed, + ) { + final keys = appStore.wallet!.keys as MoneroWalletKeys; + + final hasPublicSpendKey = commonTestCases.isKeyPresent( + '${walletName}_wallet_public_spend_key_item_key', + ); + final hasPrivateSpendKey = commonTestCases.isKeyPresent( + '${walletName}_wallet_private_spend_key_item_key', + ); + final hasPublicViewKey = commonTestCases.isKeyPresent( + '${walletName}_wallet_public_view_key_item_key', + ); + final hasPrivateViewKey = commonTestCases.isKeyPresent( + '${walletName}_wallet_private_view_key_item_key', + ); + final hasSeeds = seed.isNotEmpty; + final hasSeedLegacy = Polyseed.isValidSeed(seed); + + if (hasPublicSpendKey) { + commonTestCases.hasText(keys.publicSpendKey); + tester.printToConsole('$walletName wallet has public spend key properly displayed'); + } + if (hasPrivateSpendKey) { + commonTestCases.hasText(keys.privateSpendKey); + tester.printToConsole('$walletName wallet has private spend key properly displayed'); + } + if (hasPublicViewKey) { + commonTestCases.hasText(keys.publicViewKey); + tester.printToConsole('$walletName wallet has public view key properly displayed'); + } + if (hasPrivateViewKey) { + commonTestCases.hasText(keys.privateViewKey); + tester.printToConsole('$walletName wallet has private view key properly displayed'); + } + if (hasSeeds) { + commonTestCases.hasText(seed); + tester.printToConsole('$walletName wallet has seeds properly displayed'); + } + if (hasSeedLegacy) { + commonTestCases.hasText(legacySeed); + tester.printToConsole('$walletName wallet has legacy seeds properly displayed'); + } + } + + Future backToDashboard() async { + tester.printToConsole('Going back to dashboard from credentials page'); + await commonTestCases.goBack(); + await commonTestCases.goBack(); + } +} diff --git a/integration_test/test_suites/confirm_seeds_flow_test.dart b/integration_test/test_suites/confirm_seeds_flow_test.dart new file mode 100644 index 0000000000..1a1b855330 --- /dev/null +++ b/integration_test/test_suites/confirm_seeds_flow_test.dart @@ -0,0 +1,102 @@ +import 'package:cake_wallet/wallet_types.g.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/foundation.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../components/common_test_constants.dart'; +import '../components/common_test_flows.dart'; +import '../robots/auth_page_robot.dart'; +import '../robots/dashboard_page_robot.dart'; +import '../robots/security_and_backup_page_robot.dart'; +import '../robots/wallet_keys_robot.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + AuthPageRobot authPageRobot; + CommonTestFlows commonTestFlows; + DashboardPageRobot dashboardPageRobot; + WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot; + SecurityAndBackupPageRobot securityAndBackupPageRobot; + + testWidgets( + 'Confirm if the seeds display properly', + (tester) async { + authPageRobot = AuthPageRobot(tester); + commonTestFlows = CommonTestFlows(tester); + dashboardPageRobot = DashboardPageRobot(tester); + walletKeysAndSeedPageRobot = WalletKeysAndSeedPageRobot(tester); + securityAndBackupPageRobot = SecurityAndBackupPageRobot(tester); + + // Start the app + await commonTestFlows.startAppFlow( + ValueKey('confirm_creds_display_correctly_flow_app_key'), + ); + + await commonTestFlows.welcomePageToCreateNewWalletFlow( + WalletType.solana, + CommonTestConstants.pin, + ); + + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana); + + await _confirmSeedsFlowForWalletType( + WalletType.solana, + authPageRobot, + dashboardPageRobot, + securityAndBackupPageRobot, + walletKeysAndSeedPageRobot, + ); + + // Do the same for other available wallet types + for (var walletType in availableWalletTypes) { + if (walletType == WalletType.solana) { + continue; + } + + await commonTestFlows.switchToWalletMenuFromDashboardPage(); + + await commonTestFlows.createNewWalletFromWalletMenu(walletType); + + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType); + + await _confirmSeedsFlowForWalletType( + walletType, + authPageRobot, + dashboardPageRobot, + securityAndBackupPageRobot, + walletKeysAndSeedPageRobot, + ); + } + + await Future.delayed(Duration(seconds: 15)); + }, + ); +} + +Future _confirmSeedsFlowForWalletType( + WalletType walletType, + AuthPageRobot authPageRobot, + DashboardPageRobot dashboardPageRobot, + SecurityAndBackupPageRobot securityAndBackupPageRobot, + WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot, +) async { + await dashboardPageRobot.openDrawerMenu(); + await dashboardPageRobot.dashboardMenuWidgetRobot.navigateToSecurityAndBackupPage(); + + await securityAndBackupPageRobot.navigateToShowKeysPage(); + + final onAuthPage = authPageRobot.onAuthPage(); + if (onAuthPage) { + await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + } + + await walletKeysAndSeedPageRobot.isWalletKeysAndSeedPage(); + walletKeysAndSeedPageRobot.hasTitle(); + walletKeysAndSeedPageRobot.hasShareWarning(); + + walletKeysAndSeedPageRobot.confirmWalletCredentials(walletType); + + await walletKeysAndSeedPageRobot.backToDashboard(); +} diff --git a/ios/Podfile.lock b/ios/Podfile.lock index 56de513b66..ec3bf6a051 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -3,41 +3,10 @@ PODS: - Flutter - MTBBarcodeScanner - SwiftProtobuf - - BigInt (5.2.0) - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift - CryptoSwift (1.8.2) - - cw_haven (0.0.1): - - cw_haven/Boost (= 0.0.1) - - cw_haven/Haven (= 0.0.1) - - cw_haven/OpenSSL (= 0.0.1) - - cw_haven/Sodium (= 0.0.1) - - cw_shared_external - - Flutter - - cw_haven/Boost (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/Haven (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/OpenSSL (0.0.1): - - cw_shared_external - - Flutter - - cw_haven/Sodium (0.0.1): - - cw_shared_external - - Flutter - - cw_shared_external (0.0.1): - - cw_shared_external/Boost (= 0.0.1) - - cw_shared_external/OpenSSL (= 0.0.1) - - cw_shared_external/Sodium (= 0.0.1) - - Flutter - - cw_shared_external/Boost (0.0.1): - - Flutter - - cw_shared_external/OpenSSL (0.0.1): - - Flutter - - cw_shared_external/Sodium (0.0.1): - - Flutter - device_display_brightness (0.0.1): - Flutter - device_info_plus (0.0.1): @@ -101,8 +70,6 @@ PODS: - Flutter - MTBBarcodeScanner (5.0.11) - OrderedSet (5.0.0) - - package_info (0.0.1): - - Flutter - package_info_plus (0.4.5): - Flutter - path_provider_foundation (0.0.1): @@ -133,9 +100,6 @@ PODS: - Toast (4.1.1) - uni_links (0.0.1): - Flutter - - UnstoppableDomainsResolution (4.0.0): - - BigInt - - CryptoSwift - url_launcher_ios (0.0.1): - Flutter - wakelock_plus (0.0.1): @@ -147,8 +111,6 @@ DEPENDENCIES: - barcode_scan2 (from `.symlinks/plugins/barcode_scan2/ios`) - connectivity_plus (from `.symlinks/plugins/connectivity_plus/ios`) - CryptoSwift - - cw_haven (from `.symlinks/plugins/cw_haven/ios`) - - cw_shared_external (from `.symlinks/plugins/cw_shared_external/ios`) - device_display_brightness (from `.symlinks/plugins/device_display_brightness/ios`) - device_info_plus (from `.symlinks/plugins/device_info_plus/ios`) - devicelocale (from `.symlinks/plugins/devicelocale/ios`) @@ -161,7 +123,6 @@ DEPENDENCIES: - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - package_info (from `.symlinks/plugins/package_info/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) @@ -171,14 +132,12 @@ DEPENDENCIES: - shared_preferences_foundation (from `.symlinks/plugins/shared_preferences_foundation/darwin`) - sp_scanner (from `.symlinks/plugins/sp_scanner/ios`) - uni_links (from `.symlinks/plugins/uni_links/ios`) - - UnstoppableDomainsResolution (~> 4.0.0) - url_launcher_ios (from `.symlinks/plugins/url_launcher_ios/ios`) - wakelock_plus (from `.symlinks/plugins/wakelock_plus/ios`) - workmanager (from `.symlinks/plugins/workmanager/ios`) SPEC REPOS: https://github.com/CocoaPods/Specs.git: - - BigInt - CryptoSwift - DKImagePickerController - DKPhotoGallery @@ -190,17 +149,12 @@ SPEC REPOS: - SwiftProtobuf - SwiftyGif - Toast - - UnstoppableDomainsResolution EXTERNAL SOURCES: barcode_scan2: :path: ".symlinks/plugins/barcode_scan2/ios" connectivity_plus: :path: ".symlinks/plugins/connectivity_plus/ios" - cw_haven: - :path: ".symlinks/plugins/cw_haven/ios" - cw_shared_external: - :path: ".symlinks/plugins/cw_shared_external/ios" device_display_brightness: :path: ".symlinks/plugins/device_display_brightness/ios" device_info_plus: @@ -225,8 +179,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/in_app_review/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" - package_info: - :path: ".symlinks/plugins/package_info/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -254,11 +206,8 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0 - BigInt: f668a80089607f521586bbe29513d708491ef2f7 connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea - cw_haven: b3e54e1fbe7b8e6fda57a93206bc38f8e89b898a - cw_shared_external: 2972d872b8917603478117c9957dfca611845a92 device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 devicelocale: b22617f40038496deffba44747101255cee005b0 @@ -275,7 +224,6 @@ SPEC CHECKSUMS: integration_test: 13825b8a9334a850581300559b8839134b124670 MTBBarcodeScanner: f453b33c4b7dfe545d8c6484ed744d55671788cb OrderedSet: aaeb196f7fef5a9edf55d89760da9176ad40b93c - package_info: 873975fc26034f0b863a300ad47e7f1ac6c7ec62 package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 @@ -291,11 +239,10 @@ SPEC CHECKSUMS: SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e uni_links: d97da20c7701486ba192624d99bffaaffcfc298a - UnstoppableDomainsResolution: c3c67f4d0a5e2437cb00d4bd50c2e00d6e743841 url_launcher_ios: 5334b05cef931de560670eeae103fd3e431ac3fe wakelock_plus: 78ec7c5b202cab7761af8e2b2b3d0671be6c4ae1 workmanager: 0afdcf5628bbde6924c21af7836fed07b42e30e6 -PODFILE CHECKSUM: a2fe518be61cdbdc5b0e2da085ab543d556af2d3 +PODFILE CHECKSUM: e448f662d4c41f0c0b1ccbb78afd57dbf895a597 COCOAPODS: 1.15.2 diff --git a/lib/src/screens/wallet_keys/wallet_keys_page.dart b/lib/src/screens/wallet_keys/wallet_keys_page.dart index e2cd2f5c10..fac760516c 100644 --- a/lib/src/screens/wallet_keys/wallet_keys_page.dart +++ b/lib/src/screens/wallet_keys/wallet_keys_page.dart @@ -25,23 +25,25 @@ class WalletKeysPage extends BasePage { @override Widget trailing(BuildContext context) => IconButton( - onPressed: () async { - final url = await walletKeysViewModel.url; + key: ValueKey('wallet_keys_page_fullscreen_qr_button_key'), + onPressed: () async { + final url = await walletKeysViewModel.url; - BrightnessUtil.changeBrightnessForFunction(() async { - await Navigator.pushNamed( - context, - Routes.fullscreenQR, - arguments: QrViewData(data: url.toString(), version: QrVersions.auto), - ); - }); - }, - splashColor: Colors.transparent, - highlightColor: Colors.transparent, - hoverColor: Colors.transparent, - icon: Image.asset( - 'assets/images/qr_code_icon.png', - )); + BrightnessUtil.changeBrightnessForFunction(() async { + await Navigator.pushNamed( + context, + Routes.fullscreenQR, + arguments: QrViewData(data: url.toString(), version: QrVersions.auto), + ); + }); + }, + splashColor: Colors.transparent, + highlightColor: Colors.transparent, + hoverColor: Colors.transparent, + icon: Image.asset( + 'assets/images/qr_code_icon.png', + ), + ); @override Widget body(BuildContext context) { @@ -93,6 +95,7 @@ class WalletKeysPage extends BasePage { final item = walletKeysViewModel.items[index]; return GestureDetector( + key: item.key, onTap: () { ClipboardUtil.setSensitiveDataToClipboard(ClipboardData(text: item.value)); showBar(context, S.of(context).copied_key_to_clipboard(item.title)); diff --git a/lib/view_model/wallet_keys_view_model.dart b/lib/view_model/wallet_keys_view_model.dart index 479b633b18..24bf02626d 100644 --- a/lib/view_model/wallet_keys_view_model.dart +++ b/lib/view_model/wallet_keys_view_model.dart @@ -57,6 +57,8 @@ abstract class WalletKeysViewModelBase with Store { final String _walletName; + AppStore get appStore => _appStore; + final AppStore _appStore; final int _restoreHeight; From cc4c99eccd52bb67b1baab5bc78847ab3b1fb0e4 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 27 Aug 2024 19:13:32 +0100 Subject: [PATCH 59/69] fix: Add missing pubspec params for bitcoin and bitcoin_cash --- cw_bitcoin/pubspec.yaml | 1 + cw_bitcoin_cash/pubspec.yaml | 1 + 2 files changed, 2 insertions(+) diff --git a/cw_bitcoin/pubspec.yaml b/cw_bitcoin/pubspec.yaml index 2af1ac54e4..8971d1a861 100644 --- a/cw_bitcoin/pubspec.yaml +++ b/cw_bitcoin/pubspec.yaml @@ -63,6 +63,7 @@ dependency_overrides: # The following section is specific to Flutter. flutter: + uses-material-design: true # To add assets to your package, add an assets section, like this: # assets: diff --git a/cw_bitcoin_cash/pubspec.yaml b/cw_bitcoin_cash/pubspec.yaml index 64bd38b1d6..5dd27c7260 100644 --- a/cw_bitcoin_cash/pubspec.yaml +++ b/cw_bitcoin_cash/pubspec.yaml @@ -49,6 +49,7 @@ dependency_overrides: # The following section is specific to Flutter packages. flutter: + uses-material-design: true # To add assets to your package, add an assets section, like this: # assets: From 1979fcbe5cd7a3754a352eff824a9b774aadacaa Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 29 Aug 2024 14:07:31 +0100 Subject: [PATCH 60/69] feat: Automated Tests for Transaction History Flow --- .../robots/dashboard_page_robot.dart | 27 ++- .../test_suites/exchange_flow_test.dart | 76 ++++--- .../test_suites/send_flow_test.dart | 40 ++-- .../transaction_history_flow_test.dart | 37 ++++ .../dashboard/pages/transactions_page.dart | 185 ++++++++++-------- .../widgets/anonpay_transaction_row.dart | 1 + .../dashboard/widgets/date_section_raw.dart | 2 +- .../screens/dashboard/widgets/order_row.dart | 78 ++++---- .../screens/dashboard/widgets/trade_row.dart | 1 + .../dashboard/widgets/transaction_raw.dart | 89 ++++----- lib/src/widgets/dashboard_card_widget.dart | 1 + .../anonpay/anonpay_transactions_store.dart | 6 +- lib/store/dashboard/orders_store.dart | 24 +-- lib/store/dashboard/trades_store.dart | 19 +- .../dashboard/action_list_item.dart | 5 + .../anonpay_transaction_list_item.dart | 2 +- .../dashboard/dashboard_view_model.dart | 106 +++++++--- .../dashboard/date_section_item.dart | 2 +- .../dashboard/formatted_item_list.dart | 4 +- lib/view_model/dashboard/order_list_item.dart | 4 +- lib/view_model/dashboard/trade_list_item.dart | 6 +- .../dashboard/transaction_list_item.dart | 8 +- 22 files changed, 423 insertions(+), 300 deletions(-) create mode 100644 integration_test/test_suites/transaction_history_flow_test.dart diff --git a/integration_test/robots/dashboard_page_robot.dart b/integration_test/robots/dashboard_page_robot.dart index 9d532a7f4f..bc5f411ad4 100644 --- a/integration_test/robots/dashboard_page_robot.dart +++ b/integration_test/robots/dashboard_page_robot.dart @@ -51,28 +51,35 @@ class DashboardPageRobot { commonTestCases.hasValueKey('dashboard_page_wallet_menu_button_key'); } - Future confirmRightCryptoAssetTitleDisplaysPerPageView(WalletType type, - {bool isHaven = false}) async { + Future confirmRightCryptoAssetTitleDisplaysPerPageView( + WalletType type, { + bool isHaven = false, + }) async { //Balance Page await confirmWalletTypeIsDisplayedCorrectly(type, isHaven: isHaven); // Swipe to Cake features Page - await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false); - await commonTestCases.defaultSleepTime(); + await swipeDashboardTab(false); commonTestCases.hasText('Cake ${S.current.features}'); // Swipe back to balance - await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key'); - await commonTestCases.defaultSleepTime(); + await swipeDashboardTab(true); // Swipe to Transactions Page - await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key'); - await commonTestCases.defaultSleepTime(); + await swipeDashboardTab(true); commonTestCases.hasText(S.current.transactions); // Swipe back to balance - await commonTestCases.swipeByPageKey(key: 'dashboard_page_view_key', swipeRight: false); - await commonTestCases.defaultSleepTime(seconds: 5); + await swipeDashboardTab(false); + await commonTestCases.defaultSleepTime(seconds: 3); + } + + Future swipeDashboardTab(bool swipeRight) async { + await commonTestCases.swipeByPageKey( + key: 'dashboard_page_view_key', + swipeRight: swipeRight, + ); + await commonTestCases.defaultSleepTime(); } Future openDrawerMenu() async { diff --git a/integration_test/test_suites/exchange_flow_test.dart b/integration_test/test_suites/exchange_flow_test.dart index 4f1b9b76e1..378ef6058d 100644 --- a/integration_test/test_suites/exchange_flow_test.dart +++ b/integration_test/test_suites/exchange_flow_test.dart @@ -21,44 +21,42 @@ void main() { ExchangeTradePageRobot exchangeTradePageRobot; ExchangeConfirmPageRobot exchangeConfirmPageRobot; - group('Exchange Flow Tests', () { - testWidgets('Exchange flow', (tester) async { - authPageRobot = AuthPageRobot(tester); - commonTestFlows = CommonTestFlows(tester); - exchangePageRobot = ExchangePageRobot(tester); - dashboardPageRobot = DashboardPageRobot(tester); - exchangeTradePageRobot = ExchangeTradePageRobot(tester); - exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester); - - await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key')); - await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( - CommonTestConstants.testWalletType, - secrets.solanaTestWalletSeeds, - CommonTestConstants.pin, - ); - await dashboardPageRobot.navigateToExchangePage(); - - // ----------- Exchange Page ------------- - await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency); - await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency); - - await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount); - await exchangePageRobot.enterDepositRefundAddress( - depositAddress: CommonTestConstants.testWalletAddress, - ); - await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); - - await exchangePageRobot.onExchangeButtonPressed(); - - await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount); - - final onAuthPage = authPageRobot.onAuthPage(); - if (onAuthPage) { - await authPageRobot.enterPinCode(CommonTestConstants.pin, false); - } - - await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); - await exchangeTradePageRobot.onGotItButtonPressed(); - }); + testWidgets('Exchange flow', (tester) async { + authPageRobot = AuthPageRobot(tester); + commonTestFlows = CommonTestFlows(tester); + exchangePageRobot = ExchangePageRobot(tester); + dashboardPageRobot = DashboardPageRobot(tester); + exchangeTradePageRobot = ExchangeTradePageRobot(tester); + exchangeConfirmPageRobot = ExchangeConfirmPageRobot(tester); + + await commonTestFlows.startAppFlow(ValueKey('exchange_app_test_key')); + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( + CommonTestConstants.testWalletType, + secrets.solanaTestWalletSeeds, + CommonTestConstants.pin, + ); + await dashboardPageRobot.navigateToExchangePage(); + + // ----------- Exchange Page ------------- + await exchangePageRobot.selectDepositCurrency(CommonTestConstants.testDepositCurrency); + await exchangePageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency); + + await exchangePageRobot.enterDepositAmount(CommonTestConstants.exchangeTestAmount); + await exchangePageRobot.enterDepositRefundAddress( + depositAddress: CommonTestConstants.testWalletAddress, + ); + await exchangePageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); + + await exchangePageRobot.onExchangeButtonPressed(); + + await exchangePageRobot.handleErrors(CommonTestConstants.exchangeTestAmount); + + final onAuthPage = authPageRobot.onAuthPage(); + if (onAuthPage) { + await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + } + + await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); + await exchangeTradePageRobot.onGotItButtonPressed(); }); } diff --git a/integration_test/test_suites/send_flow_test.dart b/integration_test/test_suites/send_flow_test.dart index 963ce35484..7a46435b85 100644 --- a/integration_test/test_suites/send_flow_test.dart +++ b/integration_test/test_suites/send_flow_test.dart @@ -15,32 +15,30 @@ void main() { CommonTestFlows commonTestFlows; DashboardPageRobot dashboardPageRobot; - group('Send Flow Tests', () { - testWidgets('Send flow', (tester) async { - commonTestFlows = CommonTestFlows(tester); - sendPageRobot = SendPageRobot(tester: tester); - dashboardPageRobot = DashboardPageRobot(tester); + testWidgets('Send flow', (tester) async { + commonTestFlows = CommonTestFlows(tester); + sendPageRobot = SendPageRobot(tester: tester); + dashboardPageRobot = DashboardPageRobot(tester); - await commonTestFlows.startAppFlow(ValueKey('send_test_app_key')); - await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( - CommonTestConstants.testWalletType, - secrets.solanaTestWalletSeeds, - CommonTestConstants.pin, - ); - await dashboardPageRobot.navigateToSendPage(); + await commonTestFlows.startAppFlow(ValueKey('send_test_app_key')); + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( + CommonTestConstants.testWalletType, + secrets.solanaTestWalletSeeds, + CommonTestConstants.pin, + ); + await dashboardPageRobot.navigateToSendPage(); - await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); - await sendPageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency); - await sendPageRobot.enterAmount(CommonTestConstants.sendTestAmount); - await sendPageRobot.selectTransactionPriority(); + await sendPageRobot.enterReceiveAddress(CommonTestConstants.testWalletAddress); + await sendPageRobot.selectReceiveCurrency(CommonTestConstants.testReceiveCurrency); + await sendPageRobot.enterAmount(CommonTestConstants.sendTestAmount); + await sendPageRobot.selectTransactionPriority(); - await sendPageRobot.onSendButtonPressed(); + await sendPageRobot.onSendButtonPressed(); - await sendPageRobot.handleSendResult(); + await sendPageRobot.handleSendResult(); - await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed(); + await sendPageRobot.onSendButtonOnConfirmSendingDialogPressed(); - await sendPageRobot.onSentDialogPopUp(); - }); + await sendPageRobot.onSentDialogPopUp(); }); } diff --git a/integration_test/test_suites/transaction_history_flow_test.dart b/integration_test/test_suites/transaction_history_flow_test.dart new file mode 100644 index 0000000000..4799b56c75 --- /dev/null +++ b/integration_test/test_suites/transaction_history_flow_test.dart @@ -0,0 +1,37 @@ +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:integration_test/integration_test.dart'; + +import '../components/common_test_constants.dart'; +import '../components/common_test_flows.dart'; +import '../robots/auth_page_robot.dart'; +import '../robots/dashboard_page_robot.dart'; + +void main() { + IntegrationTestWidgetsFlutterBinding.ensureInitialized(); + + AuthPageRobot authPageRobot; + CommonTestFlows commonTestFlows; + DashboardPageRobot dashboardPageRobot; + + testWidgets('Transaction history flow', (tester) async { + authPageRobot = AuthPageRobot(tester); + commonTestFlows = CommonTestFlows(tester); + dashboardPageRobot = DashboardPageRobot(tester); + + // Start the app + await commonTestFlows.startAppFlow( + ValueKey('confirm_creds_display_correctly_flow_app_key'), + ); + + await commonTestFlows.welcomePageToCreateNewWalletFlow( + WalletType.solana, + CommonTestConstants.pin, + ); + + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana); + + await dashboardPageRobot.swipeDashboardTab(true); + }); +} diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart index 7c0e9cad42..5102351326 100644 --- a/lib/src/screens/dashboard/pages/transactions_page.dart +++ b/lib/src/screens/dashboard/pages/transactions_page.dart @@ -48,11 +48,12 @@ class TransactionsPage extends StatelessWidget { return Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 8), child: DashBoardRoundedCardWidget( + key: ValueKey('transactions_page_syncing_alert_card_key'), onTap: () { try { final uri = Uri.parse( "https://guides.cakewallet.com/docs/FAQ/why_are_my_funds_not_appearing/"); - launchUrl(uri, mode: LaunchMode.externalApplication); + launchUrl(uri, mode: LaunchMode.externalApplication); } catch (_) {} }, title: S.of(context).syncing_wallet_alert_title, @@ -64,71 +65,79 @@ class TransactionsPage extends StatelessWidget { } }), HeaderRow(dashboardViewModel: dashboardViewModel), - Expanded(child: Observer(builder: (_) { - final items = dashboardViewModel.items; + Expanded( + child: Observer( + builder: (_) { + final items = dashboardViewModel.items; - return items.isNotEmpty - ? ListView.builder( - itemCount: items.length, - itemBuilder: (context, index) { - final item = items[index]; + return items.isNotEmpty + ? ListView.builder( + itemCount: items.length, + itemBuilder: (context, index) { + final item = items[index]; - if (item is DateSectionItem) { - return DateSectionRaw(date: item.date); - } + if (item is DateSectionItem) { + return DateSectionRaw(date: item.date, key: item.key); + } - if (item is TransactionListItem) { - if (item.hasTokens && item.assetOfTransaction == null) { - return Container(); - } + if (item is TransactionListItem) { + if (item.hasTokens && item.assetOfTransaction == null) { + return Container(); + } - final transaction = item.transaction; - final transactionType = dashboardViewModel.type == WalletType.ethereum && - transaction.evmSignatureName == 'approval' - ? ' (${transaction.evmSignatureName})' - : ''; + final transaction = item.transaction; + final transactionType = + dashboardViewModel.type == WalletType.ethereum && + transaction.evmSignatureName == 'approval' + ? ' (${transaction.evmSignatureName})' + : ''; - return Observer( - builder: (_) => TransactionRow( - onTap: () => Navigator.of(context) - .pushNamed(Routes.transactionDetails, arguments: transaction), - direction: transaction.direction, - formattedDate: DateFormat('HH:mm').format(transaction.date), - formattedAmount: item.formattedCryptoAmount, - formattedFiatAmount: - dashboardViewModel.balanceViewModel.isFiatDisabled - ? '' - : item.formattedFiatAmount, - isPending: transaction.isPending, - title: item.formattedTitle + - item.formattedStatus + ' $transactionType', - ), - ); - } + return Observer( + builder: (_) => TransactionRow( + key: item.key, + onTap: () => Navigator.of(context) + .pushNamed(Routes.transactionDetails, arguments: transaction), + direction: transaction.direction, + formattedDate: DateFormat('HH:mm').format(transaction.date), + formattedAmount: item.formattedCryptoAmount, + formattedFiatAmount: + dashboardViewModel.balanceViewModel.isFiatDisabled + ? '' + : item.formattedFiatAmount, + isPending: transaction.isPending, + title: item.formattedTitle + + item.formattedStatus + + ' $transactionType', + ), + ); + } - if (item is AnonpayTransactionListItem) { - final transactionInfo = item.transaction; + if (item is AnonpayTransactionListItem) { + final transactionInfo = item.transaction; - return AnonpayTransactionRow( - onTap: () => Navigator.of(context) - .pushNamed(Routes.anonPayDetailsPage, arguments: transactionInfo), - currency: transactionInfo.fiatAmount != null - ? transactionInfo.fiatEquiv ?? '' - : CryptoCurrency.fromFullName(transactionInfo.coinTo) - .name - .toUpperCase(), - provider: transactionInfo.provider, - amount: transactionInfo.fiatAmount?.toString() ?? - (transactionInfo.amountTo?.toString() ?? ''), - createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt), - ); - } + return AnonpayTransactionRow( + key: item.key, + onTap: () => Navigator.of(context).pushNamed( + Routes.anonPayDetailsPage, + arguments: transactionInfo), + currency: transactionInfo.fiatAmount != null + ? transactionInfo.fiatEquiv ?? '' + : CryptoCurrency.fromFullName(transactionInfo.coinTo) + .name + .toUpperCase(), + provider: transactionInfo.provider, + amount: transactionInfo.fiatAmount?.toString() ?? + (transactionInfo.amountTo?.toString() ?? ''), + createdAt: DateFormat('HH:mm').format(transactionInfo.createdAt), + ); + } - if (item is TradeListItem) { - final trade = item.trade; + if (item is TradeListItem) { + final trade = item.trade; - return Observer( - builder: (_) => TradeRow( + return Observer( + builder: (_) => TradeRow( + key: item.key, onTap: () => Navigator.of(context) .pushNamed(Routes.tradeDetails, arguments: trade), provider: trade.provider, @@ -137,36 +146,44 @@ class TransactionsPage extends StatelessWidget { createdAtFormattedDate: trade.createdAt != null ? DateFormat('HH:mm').format(trade.createdAt!) : null, - formattedAmount: item.tradeFormattedAmount)); - } + formattedAmount: item.tradeFormattedAmount, + ), + ); + } - if (item is OrderListItem) { - final order = item.order; + if (item is OrderListItem) { + final order = item.order; - return Observer( - builder: (_) => OrderRow( - onTap: () => Navigator.of(context) - .pushNamed(Routes.orderDetails, arguments: order), - provider: order.provider, - from: order.from!, - to: order.to!, - createdAtFormattedDate: - DateFormat('HH:mm').format(order.createdAt), - formattedAmount: item.orderFormattedAmount, - )); - } + return Observer( + builder: (_) => OrderRow( + key: item.key, + onTap: () => Navigator.of(context) + .pushNamed(Routes.orderDetails, arguments: order), + provider: order.provider, + from: order.from!, + to: order.to!, + createdAtFormattedDate: + DateFormat('HH:mm').format(order.createdAt), + formattedAmount: item.orderFormattedAmount, + ), + ); + } - return Container(color: Colors.transparent, height: 1); - }) - : Center( - child: Text( - S.of(context).placeholder_transactions, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).extension()!.color), - ), - ); - })) + return Container(color: Colors.transparent, height: 1); + }) + : Center( + child: Text( + key: ValueKey('transactions_page_placeholder_transactions_text_key'), + S.of(context).placeholder_transactions, + style: TextStyle( + fontSize: 14, + color: Theme.of(context).extension()!.color, + ), + ), + ); + }, + ), + ) ], ), ), diff --git a/lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart b/lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart index cb8bef0b7b..64d4bfe858 100644 --- a/lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart +++ b/lib/src/screens/dashboard/widgets/anonpay_transaction_row.dart @@ -9,6 +9,7 @@ class AnonpayTransactionRow extends StatelessWidget { required this.currency, required this.onTap, required this.amount, + super.key, }); final VoidCallback? onTap; diff --git a/lib/src/screens/dashboard/widgets/date_section_raw.dart b/lib/src/screens/dashboard/widgets/date_section_raw.dart index 73f9f03a17..8ab50db994 100644 --- a/lib/src/screens/dashboard/widgets/date_section_raw.dart +++ b/lib/src/screens/dashboard/widgets/date_section_raw.dart @@ -5,7 +5,7 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/utils/date_formatter.dart'; class DateSectionRaw extends StatelessWidget { - DateSectionRaw({required this.date}); + DateSectionRaw({required this.date, super.key}); final DateTime date; diff --git a/lib/src/screens/dashboard/widgets/order_row.dart b/lib/src/screens/dashboard/widgets/order_row.dart index 8adc6e0d5e..221ea5689f 100644 --- a/lib/src/screens/dashboard/widgets/order_row.dart +++ b/lib/src/screens/dashboard/widgets/order_row.dart @@ -12,7 +12,10 @@ class OrderRow extends StatelessWidget { required this.to, required this.createdAtFormattedDate, this.onTap, - this.formattedAmount}); + this.formattedAmount, + super.key, + }); + final VoidCallback? onTap; final BuyProviderDescription provider; final String from; @@ -22,8 +25,7 @@ class OrderRow extends StatelessWidget { @override Widget build(BuildContext context) { - final iconColor = - Theme.of(context).extension()!.iconColor; + final iconColor = Theme.of(context).extension()!.iconColor; final providerIcon = getBuyProviderIcon(provider, iconColor: iconColor); @@ -36,46 +38,42 @@ class OrderRow extends StatelessWidget { mainAxisSize: MainAxisSize.max, crossAxisAlignment: CrossAxisAlignment.center, children: [ - if (providerIcon != null) Padding( - padding: EdgeInsets.only(right: 12), - child: providerIcon, - ), + if (providerIcon != null) + Padding( + padding: EdgeInsets.only(right: 12), + child: providerIcon, + ), Expanded( child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text('$from → $to', - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.textColor - )), - formattedAmount != null - ? Text(formattedAmount! + ' ' + to, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.textColor - )) - : Container() - ]), - SizedBox(height: 5), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(createdAtFormattedDate, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).extension()!.dateSectionRowColor)) - ]) - ], - ) - ) + mainAxisSize: MainAxisSize.min, + children: [ + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text('$from → $to', + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textColor)), + formattedAmount != null + ? Text(formattedAmount! + ' ' + to, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: + Theme.of(context).extension()!.textColor)) + : Container() + ]), + SizedBox(height: 5), + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text(createdAtFormattedDate, + style: TextStyle( + fontSize: 14, + color: + Theme.of(context).extension()!.dateSectionRowColor)) + ]) + ], + )) ], ), )); } -} \ No newline at end of file +} diff --git a/lib/src/screens/dashboard/widgets/trade_row.dart b/lib/src/screens/dashboard/widgets/trade_row.dart index caccb8047e..61a21ed4a7 100644 --- a/lib/src/screens/dashboard/widgets/trade_row.dart +++ b/lib/src/screens/dashboard/widgets/trade_row.dart @@ -12,6 +12,7 @@ class TradeRow extends StatelessWidget { required this.createdAtFormattedDate, this.onTap, this.formattedAmount, + super.key, }); final VoidCallback? onTap; diff --git a/lib/src/screens/dashboard/widgets/transaction_raw.dart b/lib/src/screens/dashboard/widgets/transaction_raw.dart index 3a95b9f2eb..7d4a605ba9 100644 --- a/lib/src/screens/dashboard/widgets/transaction_raw.dart +++ b/lib/src/screens/dashboard/widgets/transaction_raw.dart @@ -5,14 +5,16 @@ import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; class TransactionRow extends StatelessWidget { - TransactionRow( - {required this.direction, - required this.formattedDate, - required this.formattedAmount, - required this.formattedFiatAmount, - required this.isPending, - required this.title, - required this.onTap}); + TransactionRow({ + required this.direction, + required this.formattedDate, + required this.formattedAmount, + required this.formattedFiatAmount, + required this.isPending, + required this.title, + required this.onTap, + super.key, + }); final VoidCallback onTap; final TransactionDirection direction; @@ -38,48 +40,43 @@ class TransactionRow extends StatelessWidget { width: 36, decoration: BoxDecoration( shape: BoxShape.circle, - color: Theme.of(context).extension()!.rowsColor - ), - child: Image.asset( - direction == TransactionDirection.incoming - ? 'assets/images/down_arrow.png' - : 'assets/images/up_arrow.png'), + color: Theme.of(context).extension()!.rowsColor), + child: Image.asset(direction == TransactionDirection.incoming + ? 'assets/images/down_arrow.png' + : 'assets/images/up_arrow.png'), ), SizedBox(width: 12), Expanded( child: Column( - mainAxisSize: MainAxisSize.min, - children: [ - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(title, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.textColor)), - Text(formattedAmount, - style: TextStyle( - fontSize: 16, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.textColor)) - ]), - SizedBox(height: 5), - Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - Text(formattedDate, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).extension()!.dateSectionRowColor)), - Text(formattedFiatAmount, - style: TextStyle( - fontSize: 14, - color: Theme.of(context).extension()!.dateSectionRowColor)) - ]) - ], - ) - ) + mainAxisSize: MainAxisSize.min, + children: [ + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text(title, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textColor)), + Text(formattedAmount, + style: TextStyle( + fontSize: 16, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.textColor)) + ]), + SizedBox(height: 5), + Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [ + Text(formattedDate, + style: TextStyle( + fontSize: 14, + color: + Theme.of(context).extension()!.dateSectionRowColor)), + Text(formattedFiatAmount, + style: TextStyle( + fontSize: 14, + color: + Theme.of(context).extension()!.dateSectionRowColor)) + ]) + ], + )) ], ), )); diff --git a/lib/src/widgets/dashboard_card_widget.dart b/lib/src/widgets/dashboard_card_widget.dart index 5a8ca14a49..f04252174d 100644 --- a/lib/src/widgets/dashboard_card_widget.dart +++ b/lib/src/widgets/dashboard_card_widget.dart @@ -14,6 +14,7 @@ class DashBoardRoundedCardWidget extends StatelessWidget { this.icon, this.onClose, this.customBorder, + super.key, }); final VoidCallback onTap; diff --git a/lib/store/anonpay/anonpay_transactions_store.dart b/lib/store/anonpay/anonpay_transactions_store.dart index c6f05b9933..40cee7937f 100644 --- a/lib/store/anonpay/anonpay_transactions_store.dart +++ b/lib/store/anonpay/anonpay_transactions_store.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; @@ -27,7 +28,10 @@ abstract class AnonpayTransactionsStoreBase with Store { Future updateTransactionList() async { transactions = anonpayInvoiceInfoSource.values .map( - (transaction) => AnonpayTransactionListItem(transaction: transaction), + (transaction) => AnonpayTransactionListItem( + transaction: transaction, + key: ValueKey('anonpay_invoice_transaction_history_item_${transaction.invoiceId}_key'), + ), ) .toList(); } diff --git a/lib/store/dashboard/orders_store.dart b/lib/store/dashboard/orders_store.dart index b5ec658f77..d583a24f6e 100644 --- a/lib/store/dashboard/orders_store.dart +++ b/lib/store/dashboard/orders_store.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cake_wallet/buy/order.dart'; import 'package:cake_wallet/view_model/dashboard/order_list_item.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -10,12 +11,10 @@ part 'orders_store.g.dart'; class OrdersStore = OrdersStoreBase with _$OrdersStore; abstract class OrdersStoreBase with Store { - OrdersStoreBase({required this.ordersSource, - required this.settingsStore}) - : orders = [], - orderId = '' { - _onOrdersChanged = - ordersSource.watch().listen((_) async => await updateOrderList()); + OrdersStoreBase({required this.ordersSource, required this.settingsStore}) + : orders = [], + orderId = '' { + _onOrdersChanged = ordersSource.watch().listen((_) async => await updateOrderList()); updateOrderList(); } @@ -38,8 +37,11 @@ abstract class OrdersStoreBase with Store { void setOrder(Order order) => this.order = order; @action - Future updateOrderList() async => orders = - ordersSource.values.map((order) => OrderListItem( - order: order, - settingsStore: settingsStore)).toList(); -} \ No newline at end of file + Future updateOrderList() async => orders = ordersSource.values + .map((order) => OrderListItem( + order: order, + settingsStore: settingsStore, + key: ValueKey('orders_transaction_history_item_${order.id}_key'), + )) + .toList(); +} diff --git a/lib/store/dashboard/trades_store.dart b/lib/store/dashboard/trades_store.dart index 72442b46f5..c2d38eccf0 100644 --- a/lib/store/dashboard/trades_store.dart +++ b/lib/store/dashboard/trades_store.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:cake_wallet/exchange/trade.dart'; import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -11,9 +12,8 @@ class TradesStore = TradesStoreBase with _$TradesStore; abstract class TradesStoreBase with Store { TradesStoreBase({required this.tradesSource, required this.settingsStore}) - : trades = [] { - _onTradesChanged = - tradesSource.watch().listen((_) async => await updateTradeList()); + : trades = [] { + _onTradesChanged = tradesSource.watch().listen((_) async => await updateTradeList()); updateTradeList(); } @@ -31,8 +31,11 @@ abstract class TradesStoreBase with Store { void setTrade(Trade trade) => this.trade = trade; @action - Future updateTradeList() async => trades = - tradesSource.values.map((trade) => TradeListItem( - trade: trade, - settingsStore: settingsStore)).toList(); -} \ No newline at end of file + Future updateTradeList() async => trades = tradesSource.values + .map((trade) => TradeListItem( + trade: trade, + settingsStore: settingsStore, + key: ValueKey('trade_transaction_history_item_${trade.id}_key'), + )) + .toList(); +} diff --git a/lib/view_model/dashboard/action_list_item.dart b/lib/view_model/dashboard/action_list_item.dart index b03bd1bdc6..1ee4e6a3c2 100644 --- a/lib/view_model/dashboard/action_list_item.dart +++ b/lib/view_model/dashboard/action_list_item.dart @@ -1,3 +1,8 @@ +import 'package:flutter/foundation.dart'; + abstract class ActionListItem { + ActionListItem({required this.key}); + DateTime get date; + Key key; } \ No newline at end of file diff --git a/lib/view_model/dashboard/anonpay_transaction_list_item.dart b/lib/view_model/dashboard/anonpay_transaction_list_item.dart index 261e49070f..a54a4b3347 100644 --- a/lib/view_model/dashboard/anonpay_transaction_list_item.dart +++ b/lib/view_model/dashboard/anonpay_transaction_list_item.dart @@ -2,7 +2,7 @@ import 'package:cake_wallet/anonpay/anonpay_invoice_info.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; class AnonpayTransactionListItem extends ActionListItem { - AnonpayTransactionListItem({required this.transaction}); + AnonpayTransactionListItem({required this.transaction, required super.key}); final AnonpayInvoiceInfo transaction; diff --git a/lib/view_model/dashboard/dashboard_view_model.dart b/lib/view_model/dashboard/dashboard_view_model.dart index d58d7535ce..2443d27591 100644 --- a/lib/view_model/dashboard/dashboard_view_model.dart +++ b/lib/view_model/dashboard/dashboard_view_model.dart @@ -44,6 +44,7 @@ import 'package:cw_core/wallet_base.dart'; import 'package:cw_core/wallet_info.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:eth_sig_util/util/utils.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter/services.dart'; import 'package:mobx/mobx.dart'; import 'package:cake_wallet/bitcoin/bitcoin.dart'; @@ -164,10 +165,16 @@ abstract class DashboardViewModelBase with Store { final sortedTransactions = [..._accountTransactions]; sortedTransactions.sort((a, b) => a.date.compareTo(b.date)); - transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem( - transaction: transaction, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore))); + transactions = ObservableList.of( + sortedTransactions.map( + (transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey('monero_transaction_history_item_${transaction.id}_key'), + ), + ), + ); } else if (_wallet.type == WalletType.wownero) { subname = wow.wownero!.getCurrentAccount(_wallet).label; @@ -188,18 +195,30 @@ abstract class DashboardViewModelBase with Store { final sortedTransactions = [..._accountTransactions]; sortedTransactions.sort((a, b) => a.date.compareTo(b.date)); - transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem( - transaction: transaction, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore))); + transactions = ObservableList.of( + sortedTransactions.map( + (transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey('wownero_transaction_history_item_${transaction.id}_key'), + ), + ), + ); } else { final sortedTransactions = [...wallet.transactionHistory.transactions.values]; sortedTransactions.sort((a, b) => a.date.compareTo(b.date)); - transactions = ObservableList.of(sortedTransactions.map((transaction) => TransactionListItem( - transaction: transaction, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore))); + transactions = ObservableList.of( + sortedTransactions.map( + (transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey('${_wallet.type.name}_transaction_history_item_${transaction.id}_key'), + ), + ), + ); } // TODO: nano sub-account generation is disabled: @@ -213,9 +232,13 @@ abstract class DashboardViewModelBase with Store { appStore.wallet!.transactionHistory.transactions, transactions, (TransactionInfo? transaction) => TransactionListItem( - transaction: transaction!, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore), filter: (TransactionInfo? transaction) { + transaction: transaction!, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey( + '${_wallet.type.name}_transaction_history_item_${transaction.id}_key', + ), + ), filter: (TransactionInfo? transaction) { if (transaction == null) { return false; } @@ -576,20 +599,29 @@ abstract class DashboardViewModelBase with Store { transactions.clear(); - transactions.addAll(wallet.transactionHistory.transactions.values.map((transaction) => - TransactionListItem( - transaction: transaction, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore))); + transactions.addAll( + wallet.transactionHistory.transactions.values.map( + (transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey('${wallet.type.name}_transaction_history_item_${transaction.id}_key'), + ), + ), + ); } connectMapToListWithTransform( appStore.wallet!.transactionHistory.transactions, transactions, (TransactionInfo? transaction) => TransactionListItem( - transaction: transaction!, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore), filter: (TransactionInfo? tx) { + transaction: transaction!, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey( + '${wallet.type.name}_transaction_history_item_${transaction.id}_key', + ), + ), filter: (TransactionInfo? tx) { if (tx == null) { return false; } @@ -629,10 +661,16 @@ abstract class DashboardViewModelBase with Store { monero!.getTransactionInfoAccountId(tx) == monero!.getCurrentAccount(wallet).id) .toList(); - transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem( - transaction: transaction, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore))); + transactions.addAll( + _accountTransactions.map( + (transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey('monero_transaction_history_item_${transaction.id}_key'), + ), + ), + ); } else if (wallet.type == WalletType.wownero) { final _accountTransactions = wow.wownero! .getTransactionHistory(wallet) @@ -643,10 +681,16 @@ abstract class DashboardViewModelBase with Store { wow.wownero!.getCurrentAccount(wallet).id) .toList(); - transactions.addAll(_accountTransactions.map((transaction) => TransactionListItem( - transaction: transaction, - balanceViewModel: balanceViewModel, - settingsStore: appStore.settingsStore))); + transactions.addAll( + _accountTransactions.map( + (transaction) => TransactionListItem( + transaction: transaction, + balanceViewModel: balanceViewModel, + settingsStore: appStore.settingsStore, + key: ValueKey('wownero_transaction_history_item_${transaction.id}_key'), + ), + ), + ); } } diff --git a/lib/view_model/dashboard/date_section_item.dart b/lib/view_model/dashboard/date_section_item.dart index 0b361ecce0..75250a7ea1 100644 --- a/lib/view_model/dashboard/date_section_item.dart +++ b/lib/view_model/dashboard/date_section_item.dart @@ -1,7 +1,7 @@ import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; class DateSectionItem extends ActionListItem { - DateSectionItem(this.date); + DateSectionItem(this.date, {required super.key}); @override final DateTime date; diff --git a/lib/view_model/dashboard/formatted_item_list.dart b/lib/view_model/dashboard/formatted_item_list.dart index a1cbbbf7da..1628a811e7 100644 --- a/lib/view_model/dashboard/formatted_item_list.dart +++ b/lib/view_model/dashboard/formatted_item_list.dart @@ -11,7 +11,7 @@ List formattedItemsList(List items) { if (lastDate == null) { lastDate = transaction.date; - formattedList.add(DateSectionItem(transaction.date)); + formattedList.add(DateSectionItem(transaction.date, key: transaction.key)); formattedList.add(transaction); continue; } @@ -26,7 +26,7 @@ List formattedItemsList(List items) { } lastDate = transaction.date; - formattedList.add(DateSectionItem(transaction.date)); + formattedList.add(DateSectionItem(transaction.date, key: transaction.key)); formattedList.add(transaction); } diff --git a/lib/view_model/dashboard/order_list_item.dart b/lib/view_model/dashboard/order_list_item.dart index 9120cc1a6d..52adf53a64 100644 --- a/lib/view_model/dashboard/order_list_item.dart +++ b/lib/view_model/dashboard/order_list_item.dart @@ -6,7 +6,9 @@ import 'package:cake_wallet/entities/balance_display_mode.dart'; class OrderListItem extends ActionListItem { OrderListItem({ required this.order, - required this.settingsStore}); + required this.settingsStore, + required super.key, + }); final Order order; final SettingsStore settingsStore; diff --git a/lib/view_model/dashboard/trade_list_item.dart b/lib/view_model/dashboard/trade_list_item.dart index 964ba4ffa0..55ae4e99f3 100644 --- a/lib/view_model/dashboard/trade_list_item.dart +++ b/lib/view_model/dashboard/trade_list_item.dart @@ -4,7 +4,11 @@ import 'package:cake_wallet/store/settings_store.dart'; import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; class TradeListItem extends ActionListItem { - TradeListItem({required this.trade, required this.settingsStore}); + TradeListItem({ + required this.trade, + required this.settingsStore, + required super.key, + }); final Trade trade; final SettingsStore settingsStore; diff --git a/lib/view_model/dashboard/transaction_list_item.dart b/lib/view_model/dashboard/transaction_list_item.dart index 176b4e58d5..9725aa790d 100644 --- a/lib/view_model/dashboard/transaction_list_item.dart +++ b/lib/view_model/dashboard/transaction_list_item.dart @@ -22,8 +22,12 @@ import 'package:cw_core/keyable.dart'; import 'package:cw_core/wallet_type.dart'; class TransactionListItem extends ActionListItem with Keyable { - TransactionListItem( - {required this.transaction, required this.balanceViewModel, required this.settingsStore}); + TransactionListItem({ + required this.transaction, + required this.balanceViewModel, + required this.settingsStore, + required super.key, + }); final TransactionInfo transaction; final BalanceViewModel balanceViewModel; From 5d2785049f311200216de771c805b9012e7fb5bc Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 11 Sep 2024 04:55:09 +0100 Subject: [PATCH 61/69] fix: Add missing pubspec parameter --- cw_core/pubspec.yaml | 1 + 1 file changed, 1 insertion(+) diff --git a/cw_core/pubspec.yaml b/cw_core/pubspec.yaml index 070779caa1..6e32c2ba18 100644 --- a/cw_core/pubspec.yaml +++ b/cw_core/pubspec.yaml @@ -47,6 +47,7 @@ dependency_overrides: # The following section is specific to Flutter. flutter: + uses-material-design: true # To add assets to your package, add an assets section, like this: # assets: From 1e962710ced5d48dac42efb622b619edc01e64f4 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 18 Sep 2024 08:17:19 +0100 Subject: [PATCH 62/69] feat: Automated Integration Tests for Transaction History flow --- .../components/common_test_cases.dart | 116 ++++- .../components/common_test_flows.dart | 22 +- integration_test/funds_related_tests.dart | 2 +- .../robots/exchange_page_robot.dart | 4 +- .../robots/pin_code_widget_robot.dart | 11 +- .../restore_from_seed_or_key_robot.dart | 17 + integration_test/robots/send_page_robot.dart | 25 +- .../robots/transactions_page_robot.dart | 290 ++++++++++++ .../test_suites/confirm_seeds_flow_test.dart | 7 +- .../test_suites/exchange_flow_test.dart | 2 +- .../transaction_history_flow_test.dart | 43 +- .../dashboard/pages/transactions_page.dart | 6 +- .../dashboard/widgets/date_section_raw.dart | 39 +- .../screens/dashboard/widgets/header_row.dart | 3 +- .../wallet_restore_from_seed_form.dart | 2 + .../blockexplorer_list_item.dart | 9 +- .../rbf_details_list_fee_picker_item.dart | 26 +- .../textfield_list_item.dart | 10 +- .../transaction_details_page.dart | 60 +-- .../transaction_expandable_list_item.dart | 9 +- .../widgets/textfield_list_row.dart | 78 ++-- lib/src/widgets/blockchain_height_widget.dart | 3 + lib/src/widgets/picker.dart | 3 + .../anonpay/anonpay_transactions_store.dart | 2 +- lib/store/dashboard/orders_store.dart | 2 +- lib/store/dashboard/trades_store.dart | 2 +- lib/utils/date_formatter.dart | 26 +- lib/utils/image_utill.dart | 6 +- .../dashboard/formatted_item_list.dart | 15 +- .../transaction_details_view_model.dart | 439 ++++++++++++++---- 30 files changed, 1020 insertions(+), 259 deletions(-) create mode 100644 integration_test/robots/transactions_page_robot.dart diff --git a/integration_test/components/common_test_cases.dart b/integration_test/components/common_test_cases.dart index 7ae1c00f37..83bbb0449c 100644 --- a/integration_test/components/common_test_cases.dart +++ b/integration_test/components/common_test_cases.dart @@ -10,10 +10,16 @@ class CommonTestCases { hasType(); } - Future tapItemByKey(String key, {bool shouldPumpAndSettle = true}) async { + Future tapItemByKey( + String key, { + bool shouldPumpAndSettle = true, + int pumpDuration = 100, + }) async { final widget = find.byKey(ValueKey(key)); await tester.tap(widget); - shouldPumpAndSettle ? await tester.pumpAndSettle() : await tester.pump(); + shouldPumpAndSettle + ? await tester.pumpAndSettle(Duration(milliseconds: pumpDuration)) + : await tester.pump(); } Future tapItemByFinder(Finder finder, {bool shouldPumpAndSettle = true}) async { @@ -58,33 +64,86 @@ class CommonTestCases { await tester.pumpAndSettle(); } - Future scrollUntilVisible(String childKey, String parentScrollableKey, - {double delta = 300}) async { - final scrollableWidget = find.descendant( - of: find.byKey(Key(parentScrollableKey)), - matching: find.byType(Scrollable), - ); + Future dragUntilVisible(String childKey, String parentKey) async { + await tester.pumpAndSettle(); + + final itemFinder = find.byKey(ValueKey(childKey)); + final listFinder = find.byKey(ValueKey(parentKey)); + + // Check if the widget is already in the widget tree + if (tester.any(itemFinder)) { + // Widget is already built and in the tree + tester.printToConsole('Child is already present'); + return; + } + + // We can adjust this as needed + final maxScrolls = 200; + + int scrolls = 0; + bool found = false; - final isAlreadyVisibile = isWidgetVisible(find.byKey(ValueKey(childKey))); + // We start by scrolling down + bool scrollDown = true; - if (isAlreadyVisibile) return; + // Flag to check if we've already reversed direction + bool reversedDirection = false; - await tester.scrollUntilVisible( - find.byKey(ValueKey(childKey)), - delta, - scrollable: scrollableWidget, + // Find the Scrollable associated with the Parent Ad + final scrollableFinder = find.descendant( + of: listFinder, + matching: find.byType(Scrollable), ); - } - bool isWidgetVisible(Finder finder) { - try { - final Element element = finder.evaluate().single; - final RenderBox renderBox = element.renderObject as RenderBox; - return renderBox.paintBounds - .shift(renderBox.localToGlobal(Offset.zero)) - .overlaps(tester.binding.renderViews.first.paintBounds); - } catch (e) { - return false; + // Ensure that the Scrollable is found + expect( + scrollableFinder, + findsOneWidget, + reason: 'Scrollable descendant of the Parent Widget not found.', + ); + + // Get the initial scroll position + final scrollableState = tester.state(scrollableFinder); + double previousScrollPosition = scrollableState.position.pixels; + + while (!found && scrolls < maxScrolls) { + tester.printToConsole('Scrolling ${scrollDown ? 'down' : 'up'}, attempt $scrolls'); + + // Perform the drag in the current direction + await tester.drag( + scrollableFinder, + scrollDown ? const Offset(0, -100) : const Offset(0, 100), + ); + await tester.pumpAndSettle(); + scrolls++; + + // Update the scroll position after the drag + final currentScrollPosition = scrollableState.position.pixels; + + if (currentScrollPosition == previousScrollPosition) { + // Cannot scroll further in this direction + if (reversedDirection) { + // We've already tried both directions + tester.printToConsole('Cannot scroll further in both directions. Widget not found.'); + break; + } else { + // Reverse the scroll direction + scrollDown = !scrollDown; + reversedDirection = true; + tester.printToConsole('Reached the end, reversing direction'); + } + } else { + // Continue scrolling in the current direction + previousScrollPosition = currentScrollPosition; + } + + // Check if the widget is now in the widget tree + found = tester.any(itemFinder); + } + + if (!found) { + tester.printToConsole('Widget not found after scrolling in both directions.'); + return; } } @@ -96,6 +155,15 @@ class CommonTestCases { await tester.pumpAndSettle(); } + void findWidgetViaDescendant({ + required FinderBase of, + required FinderBase matching, + }) { + final textWidget = find.descendant(of: of, matching: matching); + + expect(textWidget, findsOneWidget); + } + Future defaultSleepTime({int seconds = 2}) async => await Future.delayed(Duration(seconds: seconds)); } diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index e523d6c0b1..8017b2447b 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -83,7 +84,7 @@ class CommonTestFlows { List walletPin, ) async { await _welcomeToRestoreFromSeedsOrKeysPath(walletTypeToRestore, walletPin); - await _restoreFromSeeds(walletSeed); + await _restoreFromSeeds(walletTypeToRestore, walletSeed); } //* ========== Handles flow from welcome to restoring wallet from keys =============== @@ -132,7 +133,7 @@ class CommonTestFlows { await _selectWalletTypeForWallet(walletType); await _commonTestCases.defaultSleepTime(); - await _restoreFromSeeds(walletSeed); + await _restoreFromSeeds(walletType, walletSeed); await _commonTestCases.defaultSleepTime(); } @@ -142,8 +143,8 @@ class CommonTestFlows { // Confirm initial defaults - Widgets to be displayed etc await _setupPinCodeRobot.isSetupPinCodePage(); - await _setupPinCodeRobot.enterPinCode(pin, true); - await _setupPinCodeRobot.enterPinCode(pin, false); + await _setupPinCodeRobot.enterPinCode(pin); + await _setupPinCodeRobot.enterPinCode(pin); await _setupPinCodeRobot.tapSuccessButton(); } @@ -214,11 +215,22 @@ class CommonTestFlows { } //* Main Restore Actions - On the RestoreFromSeed/Keys Page - Restore from Seeds Action - Future _restoreFromSeeds(String walletSeed) async { + Future _restoreFromSeeds(WalletType type, String walletSeed) async { // ----------- RestoreFromSeedOrKeys Page ------------- await _restoreFromSeedOrKeysPageRobot.selectWalletNameFromAvailableOptions(); await _restoreFromSeedOrKeysPageRobot.enterSeedPhraseForWalletRestore(walletSeed); + + final numberOfWords = walletSeed.split(' ').length; + + if (numberOfWords == 25 && (type == WalletType.monero)) { + await _restoreFromSeedOrKeysPageRobot + .chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType.legacy); + + // Using a constant value of 2831400 for the blockheight as its the restore blockheight for our testing wallet + await _restoreFromSeedOrKeysPageRobot.enterBlockHeightForWalletRestore('2831400'); + } + await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); } diff --git a/integration_test/funds_related_tests.dart b/integration_test/funds_related_tests.dart index 026d94fb2c..db24fbc0b8 100644 --- a/integration_test/funds_related_tests.dart +++ b/integration_test/funds_related_tests.dart @@ -64,7 +64,7 @@ void main() { final onAuthPage = authPageRobot.onAuthPage(); if (onAuthPage) { - await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + await authPageRobot.enterPinCode(CommonTestConstants.pin); } // ----------- Exchange Confirm Page ------------- diff --git a/integration_test/robots/exchange_page_robot.dart b/integration_test/robots/exchange_page_robot.dart index b439e47918..e01b2df9c2 100644 --- a/integration_test/robots/exchange_page_robot.dart +++ b/integration_test/robots/exchange_page_robot.dart @@ -123,7 +123,7 @@ class ExchangePageRobot { return; } - await commonTestCases.scrollUntilVisible( + await commonTestCases.dragUntilVisible( 'picker_items_index_${depositCurrency.name}_button_key', 'picker_scrollbar_key', ); @@ -149,7 +149,7 @@ class ExchangePageRobot { return; } - await commonTestCases.scrollUntilVisible( + await commonTestCases.dragUntilVisible( 'picker_items_index_${receiveCurrency.name}_button_key', 'picker_scrollbar_key', ); diff --git a/integration_test/robots/pin_code_widget_robot.dart b/integration_test/robots/pin_code_widget_robot.dart index b6805e9e01..18dc5fba46 100644 --- a/integration_test/robots/pin_code_widget_robot.dart +++ b/integration_test/robots/pin_code_widget_robot.dart @@ -24,13 +24,12 @@ class PinCodeWidgetRobot { commonTestCases.hasValueKey('pin_code_button_0_key'); } - Future pushPinButton(int index) async { - await commonTestCases.tapItemByKey('pin_code_button_${index}_key'); - } - - Future enterPinCode(List pinCode, bool isFirstEntry) async { + Future enterPinCode(List pinCode, {int pumpDuration = 100}) async { for (int pin in pinCode) { - await pushPinButton(pin); + await commonTestCases.tapItemByKey( + 'pin_code_button_${pin}_key', + pumpDuration: pumpDuration, + ); } await commonTestCases.defaultSleepTime(); diff --git a/integration_test/robots/restore_from_seed_or_key_robot.dart b/integration_test/robots/restore_from_seed_or_key_robot.dart index 43a65095d8..9d973061bf 100644 --- a/integration_test/robots/restore_from_seed_or_key_robot.dart +++ b/integration_test/robots/restore_from_seed_or_key_robot.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/src/screens/restore/wallet_restore_page.dart'; import 'package:cake_wallet/src/widgets/validable_annotated_editable_text.dart'; @@ -70,6 +71,22 @@ class RestoreFromSeedOrKeysPageRobot { await tester.pumpAndSettle(); } + Future enterBlockHeightForWalletRestore(String blockHeight) async { + await commonTestCases.enterText( + blockHeight, + 'wallet_restore_from_seed_blockheight_textfield_key', + ); + await tester.pumpAndSettle(); + } + + Future chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType selectedType) async { + await commonTestCases.tapItemByKey('wallet_restore_from_seed_seedtype_picker_button_key'); + + await commonTestCases.defaultSleepTime(); + + await commonTestCases.tapItemByKey('picker_items_index_${selectedType.title}_button_key'); + } + Future onPasteSeedPhraseButtonPressed() async { await commonTestCases.tapItemByKey('wallet_restore_from_seed_wallet_seeds_paste_button_key'); } diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index b2ecc4c12e..065eeae297 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -84,7 +84,7 @@ class SendPageRobot { return; } - await commonTestCases.scrollUntilVisible( + await commonTestCases.dragUntilVisible( 'picker_items_index_${receiveCurrency.name}_button_key', 'picker_scrollbar_key', ); @@ -117,7 +117,7 @@ class SendPageRobot { return; } - await commonTestCases.scrollUntilVisible( + await commonTestCases.dragUntilVisible( 'picker_items_index_${priority.title}_button_key', 'picker_scrollbar_key', ); @@ -140,21 +140,26 @@ class SendPageRobot { } Future _waitForSendTransactionCompletion() async { + await tester.pump(); final Completer completer = Completer(); // Loop to wait for the async operation to complete while (true) { await Future.delayed(Duration(seconds: 1)); - tester.printToConsole('Before in auth'); + tester.printToConsole('Before _handleAuth'); await _handleAuthPage(); - tester.printToConsole('After in auth'); + tester.printToConsole('After _handleAuth'); + + await tester.pump(); final sendPage = tester.widget(find.byType(SendPage)); final state = sendPage.sendViewModel.state; + await tester.pump(); + bool isDone = state is ExecutedSuccessfullyState; bool isFailed = state is FailureState; @@ -180,12 +185,20 @@ class SendPageRobot { } Future _handleAuthPage() async { + tester.printToConsole('Inside _handleAuth'); + await tester.pump(); + tester.printToConsole('starting auth checks'); + final authPage = authPageRobot.onAuthPage(); + + tester.printToConsole('hasAuth:$authPage'); + if (authPage) { - tester.printToConsole('Auth'); await tester.pump(); + tester.printToConsole('Starting inner _handleAuth loop checks'); + try { - await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + await authPageRobot.enterPinCode(CommonTestConstants.pin, pumpDuration: 500); tester.printToConsole('Auth done'); await tester.pump(); diff --git a/integration_test/robots/transactions_page_robot.dart b/integration_test/robots/transactions_page_robot.dart new file mode 100644 index 0000000000..3625727b74 --- /dev/null +++ b/integration_test/robots/transactions_page_robot.dart @@ -0,0 +1,290 @@ +import 'dart:async'; + +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/dashboard/pages/transactions_page.dart'; +import 'package:cake_wallet/utils/date_formatter.dart'; +import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; +import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart'; +import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; +import 'package:cake_wallet/view_model/dashboard/date_section_item.dart'; +import 'package:cake_wallet/view_model/dashboard/order_list_item.dart'; +import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; +import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; +import 'package:cw_core/crypto_currency.dart'; +import 'package:cw_core/sync_status.dart'; +import 'package:cw_core/transaction_direction.dart'; +import 'package:cw_core/wallet_type.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/intl.dart'; + +import '../components/common_test_cases.dart'; + +class TransactionsPageRobot { + TransactionsPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + late CommonTestCases commonTestCases; + + Future isTransactionsPage() async { + await commonTestCases.isSpecificPage(); + } + + Future confirmTransactionsPageConstantsDisplayProperly() async { + await commonTestCases.defaultSleepTime(); + + final transactionsPage = tester.widget(find.byType(TransactionsPage)); + final dashboardViewModel = transactionsPage.dashboardViewModel; + if (dashboardViewModel.status is SyncingSyncStatus) { + commonTestCases.hasValueKey('transactions_page_syncing_alert_card_key'); + commonTestCases.hasText(S.current.syncing_wallet_alert_title); + commonTestCases.hasText(S.current.syncing_wallet_alert_content); + } + + commonTestCases.hasValueKey('transactions_page_header_row_key'); + commonTestCases.hasText(S.current.transactions); + commonTestCases.hasValueKey('transactions_page_header_row_transaction_filter_button_key'); + } + + Future confirmTransactionHistoryListDisplaysCorrectly(bool hasTxHistoryWhileSyncing) async { + // Retrieve the TransactionsPage widget and its DashboardViewModel + final transactionsPage = tester.widget(find.byType(TransactionsPage)); + final dashboardViewModel = transactionsPage.dashboardViewModel; + + // Define a timeout to prevent infinite loops + // Putting at one hour for cases like monero that takes time to sync + final timeout = Duration(hours: 1); + final pollingInterval = Duration(seconds: 2); + final endTime = DateTime.now().add(timeout); + + while (DateTime.now().isBefore(endTime)) { + final isSynced = dashboardViewModel.status is SyncedSyncStatus; + final itemsLoaded = dashboardViewModel.items.isNotEmpty; + + // Perform item checks if items are loaded + if (itemsLoaded) { + await _performItemChecks(dashboardViewModel); + } else { + // Verify placeholder when items are not loaded + _verifyPlaceholder(); + } + + // Determine if we should exit the loop + if (_shouldExitLoop(hasTxHistoryWhileSyncing, isSynced, itemsLoaded)) { + break; + } + + // Pump the UI and wait for the next polling interval + await tester.pump(pollingInterval); + } + + // After the loop, verify that both status is synced and items are loaded + if (!_isFinalStateValid(dashboardViewModel)) { + throw TimeoutException('Dashboard did not sync and load items within the allotted time.'); + } + } + + bool _shouldExitLoop(bool hasTxHistoryWhileSyncing, bool isSynced, bool itemsLoaded) { + if (hasTxHistoryWhileSyncing) { + // When hasTxHistoryWhileSyncing is true, exit when status is synced + return isSynced; + } else { + // When hasTxHistoryWhileSyncing is false, exit when status is synced and items are loaded + return isSynced && itemsLoaded; + } + } + + void _verifyPlaceholder() { + commonTestCases.hasValueKey('transactions_page_placeholder_transactions_text_key'); + commonTestCases.hasText(S.current.placeholder_transactions); + } + + bool _isFinalStateValid(DashboardViewModel dashboardViewModel) { + final isSynced = dashboardViewModel.status is SyncedSyncStatus; + final itemsLoaded = dashboardViewModel.items.isNotEmpty; + return isSynced && itemsLoaded; + } + + Future _performItemChecks(DashboardViewModel dashboardViewModel) async { + List items = dashboardViewModel.items; + for (var item in items) { + final keyId = (item.key as ValueKey).value; + tester.printToConsole('\n'); + tester.printToConsole(keyId); + + await commonTestCases.dragUntilVisible(keyId, 'transactions_page_list_view_builder_key'); + await tester.pump(); + + final isWidgetVisible = tester.any(find.byKey(ValueKey(keyId))); + if (!isWidgetVisible) { + tester.printToConsole('Moving to next visible item on list'); + continue; + } + ; + await tester.pump(); + + if (item is DateSectionItem) { + await _verifyDateSectionItem(item); + } else if (item is TransactionListItem) { + tester.printToConsole(item.formattedTitle); + tester.printToConsole(item.formattedFiatAmount); + tester.printToConsole('\n'); + await _verifyTransactionListItemDisplay(item, dashboardViewModel); + } else if (item is AnonpayTransactionListItem) { + await _verifyAnonpayTransactionListItemDisplay(item); + } else if (item is TradeListItem) { + await _verifyTradeListItemDisplay(item); + } else if (item is OrderListItem) { + await _verifyOrderListItemDisplay(item); + } + } + } + + Future _verifyDateSectionItem(DateSectionItem item) async { + final title = DateFormatter.convertDateTimeToReadableString(item.date); + tester.printToConsole(title); + await tester.pump(); + + commonTestCases.findWidgetViaDescendant( + of: find.byKey(item.key), + matching: find.text(title), + ); + } + + Future _verifyTransactionListItemDisplay( + TransactionListItem item, + DashboardViewModel dashboardViewModel, + ) async { + final keyId = + '${dashboardViewModel.type.name}_transaction_history_item_${item.transaction.id}_key'; + + if (item.hasTokens && item.assetOfTransaction == null) return; + + //* ==============Confirm it has the right key for this item ======== + commonTestCases.hasValueKey(keyId); + + //* ======Confirm it displays the properly formatted amount========== + commonTestCases.findWidgetViaDescendant( + of: find.byKey(ValueKey(keyId)), + matching: find.text(item.formattedCryptoAmount), + ); + + //* ======Confirm it displays the properly formatted title=========== + final transactionType = dashboardViewModel.type == WalletType.ethereum && + item.transaction.evmSignatureName == 'approval' + ? ' (${item.transaction.evmSignatureName})' + : ''; + + final title = item.formattedTitle + item.formattedStatus + ' $transactionType'; + + commonTestCases.findWidgetViaDescendant( + of: find.byKey(ValueKey(keyId)), + matching: find.text(title), + ); + + //* ======Confirm it displays the properly formatted date============ + final formattedDate = DateFormat('HH:mm').format(item.transaction.date); + commonTestCases.findWidgetViaDescendant( + of: find.byKey(ValueKey(keyId)), + matching: find.text(formattedDate), + ); + + //* ======Confirm it displays the properly formatted fiat amount===== + final formattedFiatAmount = + dashboardViewModel.balanceViewModel.isFiatDisabled ? '' : item.formattedFiatAmount; + if (formattedFiatAmount.isNotEmpty) { + commonTestCases.findWidgetViaDescendant( + of: find.byKey(ValueKey(keyId)), + matching: find.text(formattedFiatAmount), + ); + } + + //* ======Confirm it displays the right image based on the transaction direction===== + final imageToUse = item.transaction.direction == TransactionDirection.incoming + ? 'assets/images/down_arrow.png' + : 'assets/images/up_arrow.png'; + + find.widgetWithImage(Container, AssetImage(imageToUse)); + } + + Future _verifyAnonpayTransactionListItemDisplay(AnonpayTransactionListItem item) async { + final keyId = 'anonpay_invoice_transaction_list_item_${item.transaction.invoiceId}_key'; + + //* ==============Confirm it has the right key for this item ======== + commonTestCases.hasValueKey(keyId); + + //* ==============Confirm it displays the correct provider ========================= + commonTestCases.hasText(item.transaction.provider); + + //* ===========Confirm it displays the properly formatted amount with currency ======== + final currency = item.transaction.fiatAmount != null + ? item.transaction.fiatEquiv ?? '' + : CryptoCurrency.fromFullName(item.transaction.coinTo).name.toUpperCase(); + + final amount = + item.transaction.fiatAmount?.toString() ?? (item.transaction.amountTo?.toString() ?? ''); + + final amountCurrencyText = amount + ' ' + currency; + + commonTestCases.hasText(amountCurrencyText); + + //* ======Confirm it displays the properly formatted date================= + final formattedDate = DateFormat('HH:mm').format(item.transaction.createdAt); + commonTestCases.hasText(formattedDate); + + //* ===============Confirm it displays the right image==================== + find.widgetWithImage(ClipRRect, AssetImage('assets/images/trocador.png')); + } + + Future _verifyTradeListItemDisplay(TradeListItem item) async { + final keyId = 'trade_list_item_${item.trade.id}_key'; + + //* ==============Confirm it has the right key for this item ======== + commonTestCases.hasValueKey(keyId); + + //* ==============Confirm it displays the correct provider ========================= + final conversionFlow = '${item.trade.from.toString()} → ${item.trade.to.toString()}'; + + commonTestCases.hasText(conversionFlow); + + //* ===========Confirm it displays the properly formatted amount with its crypto tag ======== + + final amountCryptoText = item.tradeFormattedAmount + ' ' + item.trade.from.toString(); + + commonTestCases.hasText(amountCryptoText); + + //* ======Confirm it displays the properly formatted date================= + final createdAtFormattedDate = + item.trade.createdAt != null ? DateFormat('HH:mm').format(item.trade.createdAt!) : null; + + if (createdAtFormattedDate != null) { + commonTestCases.hasText(createdAtFormattedDate); + } + + //* ===============Confirm it displays the right image==================== + commonTestCases.hasValueKey(item.trade.provider.image); + } + + Future _verifyOrderListItemDisplay(OrderListItem item) async { + final keyId = 'order_list_item_${item.order.id}_key'; + + //* ==============Confirm it has the right key for this item ======== + commonTestCases.hasValueKey(keyId); + + //* ==============Confirm it displays the correct provider ========================= + final orderFlow = '${item.order.from!} → ${item.order.to}'; + + commonTestCases.hasText(orderFlow); + + //* ===========Confirm it displays the properly formatted amount with its crypto tag ======== + + final amountCryptoText = item.orderFormattedAmount + ' ' + item.order.to!; + + commonTestCases.hasText(amountCryptoText); + + //* ======Confirm it displays the properly formatted date================= + final createdAtFormattedDate = DateFormat('HH:mm').format(item.order.createdAt); + + commonTestCases.hasText(createdAtFormattedDate); + } +} diff --git a/integration_test/test_suites/confirm_seeds_flow_test.dart b/integration_test/test_suites/confirm_seeds_flow_test.dart index 1a1b855330..bf6fd5a5f3 100644 --- a/integration_test/test_suites/confirm_seeds_flow_test.dart +++ b/integration_test/test_suites/confirm_seeds_flow_test.dart @@ -47,6 +47,7 @@ void main() { dashboardPageRobot, securityAndBackupPageRobot, walletKeysAndSeedPageRobot, + tester, ); // Do the same for other available wallet types @@ -67,6 +68,7 @@ void main() { dashboardPageRobot, securityAndBackupPageRobot, walletKeysAndSeedPageRobot, + tester, ); } @@ -81,6 +83,7 @@ Future _confirmSeedsFlowForWalletType( DashboardPageRobot dashboardPageRobot, SecurityAndBackupPageRobot securityAndBackupPageRobot, WalletKeysAndSeedPageRobot walletKeysAndSeedPageRobot, + WidgetTester tester, ) async { await dashboardPageRobot.openDrawerMenu(); await dashboardPageRobot.dashboardMenuWidgetRobot.navigateToSecurityAndBackupPage(); @@ -89,9 +92,11 @@ Future _confirmSeedsFlowForWalletType( final onAuthPage = authPageRobot.onAuthPage(); if (onAuthPage) { - await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + await authPageRobot.enterPinCode(CommonTestConstants.pin); } + await tester.pumpAndSettle(); + await walletKeysAndSeedPageRobot.isWalletKeysAndSeedPage(); walletKeysAndSeedPageRobot.hasTitle(); walletKeysAndSeedPageRobot.hasShareWarning(); diff --git a/integration_test/test_suites/exchange_flow_test.dart b/integration_test/test_suites/exchange_flow_test.dart index 378ef6058d..c36ef93962 100644 --- a/integration_test/test_suites/exchange_flow_test.dart +++ b/integration_test/test_suites/exchange_flow_test.dart @@ -53,7 +53,7 @@ void main() { final onAuthPage = authPageRobot.onAuthPage(); if (onAuthPage) { - await authPageRobot.enterPinCode(CommonTestConstants.pin, false); + await authPageRobot.enterPinCode(CommonTestConstants.pin); } await exchangeConfirmPageRobot.onSavedTradeIdButtonPressed(); diff --git a/integration_test/test_suites/transaction_history_flow_test.dart b/integration_test/test_suites/transaction_history_flow_test.dart index 4799b56c75..8af6d39fd6 100644 --- a/integration_test/test_suites/transaction_history_flow_test.dart +++ b/integration_test/test_suites/transaction_history_flow_test.dart @@ -5,33 +5,66 @@ import 'package:integration_test/integration_test.dart'; import '../components/common_test_constants.dart'; import '../components/common_test_flows.dart'; -import '../robots/auth_page_robot.dart'; import '../robots/dashboard_page_robot.dart'; +import '../robots/transactions_page_robot.dart'; +import 'package:cake_wallet/.secrets.g.dart' as secrets; void main() { IntegrationTestWidgetsFlutterBinding.ensureInitialized(); - AuthPageRobot authPageRobot; CommonTestFlows commonTestFlows; DashboardPageRobot dashboardPageRobot; + TransactionsPageRobot transactionsPageRobot; + /// Two Test Scenarios + /// - Fully Synchronizes and display the transaction history either immediately or few seconds after fully synchronizing + /// - Displays the transaction history progressively as synchronizing happens testWidgets('Transaction history flow', (tester) async { - authPageRobot = AuthPageRobot(tester); commonTestFlows = CommonTestFlows(tester); dashboardPageRobot = DashboardPageRobot(tester); + transactionsPageRobot = TransactionsPageRobot(tester); - // Start the app await commonTestFlows.startAppFlow( ValueKey('confirm_creds_display_correctly_flow_app_key'), ); - await commonTestFlows.welcomePageToCreateNewWalletFlow( + /// Test Scenario 1 - Displays transaction history list after fully synchronizing. + /// + /// For Solana/Tron WalletTypes. + await commonTestFlows.welcomePageToRestoreWalletThroughSeedsFlow( WalletType.solana, + secrets.solanaTestWalletSeeds, CommonTestConstants.pin, ); await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.solana); await dashboardPageRobot.swipeDashboardTab(true); + + await transactionsPageRobot.isTransactionsPage(); + + await transactionsPageRobot.confirmTransactionsPageConstantsDisplayProperly(); + + await transactionsPageRobot.confirmTransactionHistoryListDisplaysCorrectly(false); + + /// Test Scenario 2 - Displays transaction history list while synchronizing. + /// + /// For bitcoin/Monero/Wownero WalletTypes. + await commonTestFlows.switchToWalletMenuFromDashboardPage(); + + await commonTestFlows.restoreWalletFromWalletMenu( + WalletType.bitcoin, + secrets.bitcoinTestWalletSeeds, + ); + + await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(WalletType.bitcoin); + + await dashboardPageRobot.swipeDashboardTab(true); + + await transactionsPageRobot.isTransactionsPage(); + + await transactionsPageRobot.confirmTransactionsPageConstantsDisplayProperly(); + + await transactionsPageRobot.confirmTransactionHistoryListDisplaysCorrectly(true); }); } diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart index 5102351326..c0584f2f5f 100644 --- a/lib/src/screens/dashboard/pages/transactions_page.dart +++ b/lib/src/screens/dashboard/pages/transactions_page.dart @@ -64,7 +64,10 @@ class TransactionsPage extends StatelessWidget { return Container(); } }), - HeaderRow(dashboardViewModel: dashboardViewModel), + HeaderRow( + dashboardViewModel: dashboardViewModel, + key: ValueKey('transactions_page_header_row_key'), + ), Expanded( child: Observer( builder: (_) { @@ -72,6 +75,7 @@ class TransactionsPage extends StatelessWidget { return items.isNotEmpty ? ListView.builder( + key: ValueKey('transactions_page_list_view_builder_key'), itemCount: items.length, itemBuilder: (context, index) { final item = items[index]; diff --git a/lib/src/screens/dashboard/widgets/date_section_raw.dart b/lib/src/screens/dashboard/widgets/date_section_raw.dart index 8ab50db994..02fcef7f48 100644 --- a/lib/src/screens/dashboard/widgets/date_section_raw.dart +++ b/lib/src/screens/dashboard/widgets/date_section_raw.dart @@ -1,7 +1,5 @@ import 'package:flutter/material.dart'; import 'package:cake_wallet/themes/extensions/cake_text_theme.dart'; -import 'package:intl/intl.dart'; -import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/utils/date_formatter.dart'; class DateSectionRaw extends StatelessWidget { @@ -11,32 +9,19 @@ class DateSectionRaw extends StatelessWidget { @override Widget build(BuildContext context) { - final nowDate = DateTime.now(); - final diffDays = date.difference(nowDate).inDays; - final isToday = nowDate.day == date.day && - nowDate.month == date.month && - nowDate.year == date.year; - final dateSectionDateFormat = DateFormatter.withCurrentLocal(hasTime: false); - var title = ""; - - if (isToday) { - title = S.of(context).today; - } else if (diffDays == 0) { - title = S.of(context).yesterday; - } else if (diffDays > -7 && diffDays < 0) { - final dateFormat = DateFormat.EEEE(); - title = dateFormat.format(date); - } else { - title = dateSectionDateFormat.format(date); - } + final title = DateFormatter.convertDateTimeToReadableString(date); return Container( - height: 35, - alignment: Alignment.center, - color: Colors.transparent, - child: Text(title, - style: TextStyle( - fontSize: 12, - color: Theme.of(context).extension()!.dateSectionRowColor))); + height: 35, + alignment: Alignment.center, + color: Colors.transparent, + child: Text( + title, + style: TextStyle( + fontSize: 12, + color: Theme.of(context).extension()!.dateSectionRowColor, + ), + ), + ); } } diff --git a/lib/src/screens/dashboard/widgets/header_row.dart b/lib/src/screens/dashboard/widgets/header_row.dart index cb4f67fc29..f1f3f08894 100644 --- a/lib/src/screens/dashboard/widgets/header_row.dart +++ b/lib/src/screens/dashboard/widgets/header_row.dart @@ -7,7 +7,7 @@ import 'package:cake_wallet/view_model/dashboard/dashboard_view_model.dart'; import 'package:cake_wallet/themes/extensions/dashboard_page_theme.dart'; class HeaderRow extends StatelessWidget { - HeaderRow({required this.dashboardViewModel}); + HeaderRow({required this.dashboardViewModel, super.key}); final DashboardViewModel dashboardViewModel; @@ -34,6 +34,7 @@ class HeaderRow extends StatelessWidget { Semantics( container: true, child: GestureDetector( + key: ValueKey('transactions_page_header_row_transaction_filter_button_key'), onTap: () { showPopUp( context: context, diff --git a/lib/src/screens/restore/wallet_restore_from_seed_form.dart b/lib/src/screens/restore/wallet_restore_from_seed_form.dart index 67576144c3..97333a0605 100644 --- a/lib/src/screens/restore/wallet_restore_from_seed_form.dart +++ b/lib/src/screens/restore/wallet_restore_from_seed_form.dart @@ -201,6 +201,7 @@ class WalletRestoreFromSeedFormState extends State { ), if (widget.type == WalletType.monero || widget.type == WalletType.wownero) GestureDetector( + key: ValueKey('wallet_restore_from_seed_seedtype_picker_button_key'), onTap: () async { await showPopUp( context: context, @@ -274,6 +275,7 @@ class WalletRestoreFromSeedFormState extends State { BlockchainHeightWidget( focusNode: widget.blockHeightFocusNode, key: blockchainHeightKey, + blockHeightTextFieldKey: ValueKey('wallet_restore_from_seed_blockheight_textfield_key'), onHeightOrDateEntered: widget.onHeightOrDateEntered, hasDatePicker: widget.type == WalletType.monero || widget.type == WalletType.wownero, walletType: widget.type, diff --git a/lib/src/screens/transaction_details/blockexplorer_list_item.dart b/lib/src/screens/transaction_details/blockexplorer_list_item.dart index d5a70daa77..0126a147a1 100644 --- a/lib/src/screens/transaction_details/blockexplorer_list_item.dart +++ b/lib/src/screens/transaction_details/blockexplorer_list_item.dart @@ -1,7 +1,12 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; +import 'package:flutter/foundation.dart'; class BlockExplorerListItem extends TransactionDetailsListItem { - BlockExplorerListItem({required String title, required String value, required this.onTap}) - : super(title: title, value: value); + BlockExplorerListItem({ + required String title, + required String value, + required this.onTap, + Key? key, + }) : super(title: title, value: value, key: key); final Function() onTap; } diff --git a/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart b/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart index db3d945008..96ddc94cdd 100644 --- a/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart +++ b/lib/src/screens/transaction_details/rbf_details_list_fee_picker_item.dart @@ -1,18 +1,20 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; +import 'package:flutter/widgets.dart'; class StandardPickerListItem extends TransactionDetailsListItem { - StandardPickerListItem( - {required String title, - required String value, - required this.items, - required this.displayItem, - required this.onSliderChanged, - required this.onItemSelected, - required this.selectedIdx, - required this.customItemIndex, - this.maxValue, - required this.customValue}) - : super(title: title, value: value); + StandardPickerListItem({ + required String title, + required String value, + required this.items, + required this.displayItem, + required this.onSliderChanged, + required this.onItemSelected, + required this.selectedIdx, + required this.customItemIndex, + this.maxValue, + required this.customValue, + Key? key, + }) : super(title: title, value: value, key: key); final List items; final String Function(T item, double sliderValue) displayItem; diff --git a/lib/src/screens/transaction_details/textfield_list_item.dart b/lib/src/screens/transaction_details/textfield_list_item.dart index 49ef2705e8..846f9acd5d 100644 --- a/lib/src/screens/transaction_details/textfield_list_item.dart +++ b/lib/src/screens/transaction_details/textfield_list_item.dart @@ -1,11 +1,17 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; +import 'package:flutter/foundation.dart'; class TextFieldListItem extends TransactionDetailsListItem { TextFieldListItem({ required String title, required String value, - required this.onSubmitted}) - : super(title: title, value: value); + required this.onSubmitted, + Key? key, + }) : super( + title: title, + value: value, + key: key, + ); final Function(String value) onSubmitted; } \ No newline at end of file diff --git a/lib/src/screens/transaction_details/transaction_details_page.dart b/lib/src/screens/transaction_details/transaction_details_page.dart index d06b935dd6..56c612b7bd 100644 --- a/lib/src/screens/transaction_details/transaction_details_page.dart +++ b/lib/src/screens/transaction_details/transaction_details_page.dart @@ -33,38 +33,42 @@ class TransactionDetailsPage extends BasePage { children: [ Expanded( child: SectionStandardList( - sectionCount: 1, - itemCounter: (int _) => transactionDetailsViewModel.items.length, - itemBuilder: (__, index) { - final item = transactionDetailsViewModel.items[index]; + sectionCount: 1, + itemCounter: (int _) => transactionDetailsViewModel.items.length, + itemBuilder: (__, index) { + final item = transactionDetailsViewModel.items[index]; - if (item is StandartListItem) { - return GestureDetector( - onTap: () { - Clipboard.setData(ClipboardData(text: item.value)); - showBar(context, S.of(context).transaction_details_copied(item.title)); - }, - child: ListRow(title: '${item.title}:', value: item.value), - ); - } + if (item is StandartListItem) { + return GestureDetector( + key: item.key, + onTap: () { + Clipboard.setData(ClipboardData(text: item.value)); + showBar(context, S.of(context).transaction_details_copied(item.title)); + }, + child: ListRow(title: '${item.title}:', value: item.value), + ); + } - if (item is BlockExplorerListItem) { - return GestureDetector( - onTap: item.onTap, - child: ListRow(title: '${item.title}:', value: item.value), - ); - } + if (item is BlockExplorerListItem) { + return GestureDetector( + key: item.key, + onTap: item.onTap, + child: ListRow(title: '${item.title}:', value: item.value), + ); + } - if (item is TextFieldListItem) { - return TextFieldListRow( - title: item.title, - value: item.value, - onSubmitted: item.onSubmitted, - ); - } + if (item is TextFieldListItem) { + return TextFieldListRow( + key: item.key, + title: item.title, + value: item.value, + onSubmitted: item.onSubmitted, + ); + } - return Container(); - }), + return Container(); + }, + ), ), Observer( builder: (_) { diff --git a/lib/src/screens/transaction_details/transaction_expandable_list_item.dart b/lib/src/screens/transaction_details/transaction_expandable_list_item.dart index e87405de3f..db6cf22ae6 100644 --- a/lib/src/screens/transaction_details/transaction_expandable_list_item.dart +++ b/lib/src/screens/transaction_details/transaction_expandable_list_item.dart @@ -1,7 +1,12 @@ import 'package:cake_wallet/src/screens/transaction_details/transaction_details_list_item.dart'; +import 'package:flutter/foundation.dart'; class StandardExpandableListItem extends TransactionDetailsListItem { - StandardExpandableListItem({required String title, required this.expandableItems}) - : super(title: title, value: ''); + StandardExpandableListItem({ + required String title, + required this.expandableItems, + Key? key, + }) : super(title: title, value: '', key: key); + final List expandableItems; } diff --git a/lib/src/screens/transaction_details/widgets/textfield_list_row.dart b/lib/src/screens/transaction_details/widgets/textfield_list_row.dart index ff5513502c..89dcf1f236 100644 --- a/lib/src/screens/transaction_details/widgets/textfield_list_row.dart +++ b/lib/src/screens/transaction_details/widgets/textfield_list_row.dart @@ -4,14 +4,15 @@ import 'package:cake_wallet/generated/i18n.dart'; import 'package:cake_wallet/themes/extensions/transaction_trade_theme.dart'; class TextFieldListRow extends StatelessWidget { - TextFieldListRow( - {required this.title, - required this.value, - this.titleFontSize = 14, - this.valueFontSize = 16, - this.onSubmitted, - this.onTapOutside}) - : _textController = TextEditingController() { + TextFieldListRow({ + required this.title, + required this.value, + this.titleFontSize = 14, + this.valueFontSize = 16, + this.onSubmitted, + this.onTapOutside, + super.key, + }) : _textController = TextEditingController() { _textController.text = value; } @@ -29,41 +30,38 @@ class TextFieldListRow extends StatelessWidget { width: double.infinity, color: Theme.of(context).colorScheme.background, child: Padding( - padding: - const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24), - child: Column( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - Text(title, - style: TextStyle( - fontSize: titleFontSize, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.detailsTitlesColor), - textAlign: TextAlign.left), - TextField( - controller: _textController, - keyboardType: TextInputType.multiline, - textInputAction: TextInputAction.done, - maxLines: null, - textAlign: TextAlign.start, - style: TextStyle( + padding: const EdgeInsets.only(left: 24, top: 16, bottom: 16, right: 24), + child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [ + Text(title, + style: TextStyle( + fontSize: titleFontSize, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.detailsTitlesColor), + textAlign: TextAlign.left), + TextField( + controller: _textController, + keyboardType: TextInputType.multiline, + textInputAction: TextInputAction.done, + maxLines: null, + textAlign: TextAlign.start, + style: TextStyle( + fontSize: valueFontSize, + fontWeight: FontWeight.w500, + color: Theme.of(context).extension()!.titleColor), + decoration: InputDecoration( + isDense: true, + contentPadding: EdgeInsets.only(top: 12, bottom: 0), + hintText: S.of(context).enter_your_note, + hintStyle: TextStyle( fontSize: valueFontSize, fontWeight: FontWeight.w500, color: - Theme.of(context).extension()!.titleColor), - decoration: InputDecoration( - isDense: true, - contentPadding: EdgeInsets.only(top: 12, bottom: 0), - hintText: S.of(context).enter_your_note, - hintStyle: TextStyle( - fontSize: valueFontSize, - fontWeight: FontWeight.w500, - color: Theme.of(context).extension()!.detailsTitlesColor), - border: InputBorder.none), - onTapOutside: (_) => onTapOutside?.call(_textController.text), - onSubmitted: (value) => onSubmitted?.call(value), - ) - ]), + Theme.of(context).extension()!.detailsTitlesColor), + border: InputBorder.none), + onTapOutside: (_) => onTapOutside?.call(_textController.text), + onSubmitted: (value) => onSubmitted?.call(value), + ) + ]), ), ); } diff --git a/lib/src/widgets/blockchain_height_widget.dart b/lib/src/widgets/blockchain_height_widget.dart index e0f83a4f47..73ace57cea 100644 --- a/lib/src/widgets/blockchain_height_widget.dart +++ b/lib/src/widgets/blockchain_height_widget.dart @@ -21,6 +21,7 @@ class BlockchainHeightWidget extends StatefulWidget { this.toggleSingleScan, this.doSingleScan = false, required this.walletType, + this.blockHeightTextFieldKey, }) : super(key: key); final Function(int)? onHeightChange; @@ -31,6 +32,7 @@ class BlockchainHeightWidget extends StatefulWidget { final bool doSingleScan; final Function()? toggleSingleScan; final WalletType walletType; + final Key? blockHeightTextFieldKey; @override State createState() => BlockchainHeightState(); @@ -77,6 +79,7 @@ class BlockchainHeightState extends State { child: Container( padding: EdgeInsets.only(top: 20.0, bottom: 10.0), child: BaseTextFormField( + key: widget.blockHeightTextFieldKey, focusNode: widget.focusNode, controller: restoreHeightController, keyboardType: TextInputType.numberWithOptions(signed: false, decimal: false), diff --git a/lib/src/widgets/picker.dart b/lib/src/widgets/picker.dart index 801a795950..ed5c05be60 100644 --- a/lib/src/widgets/picker.dart +++ b/lib/src/widgets/picker.dart @@ -2,6 +2,7 @@ import 'dart:math'; +import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/src/widgets/search_bar_widget.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; import 'package:cw_core/transaction_priority.dart'; @@ -310,6 +311,8 @@ class _PickerState extends State> { itemName = item.name; } else if (item is TransactionPriority) { itemName = item.title; + } else if (item is MoneroSeedType) { + itemName = item.title; } else { itemName = ''; } diff --git a/lib/store/anonpay/anonpay_transactions_store.dart b/lib/store/anonpay/anonpay_transactions_store.dart index 40cee7937f..d0384ca5a4 100644 --- a/lib/store/anonpay/anonpay_transactions_store.dart +++ b/lib/store/anonpay/anonpay_transactions_store.dart @@ -30,7 +30,7 @@ abstract class AnonpayTransactionsStoreBase with Store { .map( (transaction) => AnonpayTransactionListItem( transaction: transaction, - key: ValueKey('anonpay_invoice_transaction_history_item_${transaction.invoiceId}_key'), + key: ValueKey('anonpay_invoice_transaction_list_item_${transaction.invoiceId}_key'), ), ) .toList(); diff --git a/lib/store/dashboard/orders_store.dart b/lib/store/dashboard/orders_store.dart index d583a24f6e..d91ad3512a 100644 --- a/lib/store/dashboard/orders_store.dart +++ b/lib/store/dashboard/orders_store.dart @@ -41,7 +41,7 @@ abstract class OrdersStoreBase with Store { .map((order) => OrderListItem( order: order, settingsStore: settingsStore, - key: ValueKey('orders_transaction_history_item_${order.id}_key'), + key: ValueKey('order_list_item_${order.id}_key'), )) .toList(); } diff --git a/lib/store/dashboard/trades_store.dart b/lib/store/dashboard/trades_store.dart index c2d38eccf0..d0a4592e33 100644 --- a/lib/store/dashboard/trades_store.dart +++ b/lib/store/dashboard/trades_store.dart @@ -35,7 +35,7 @@ abstract class TradesStoreBase with Store { .map((trade) => TradeListItem( trade: trade, settingsStore: settingsStore, - key: ValueKey('trade_transaction_history_item_${trade.id}_key'), + key: ValueKey('trade_list_item_${trade.id}_key'), )) .toList(); } diff --git a/lib/utils/date_formatter.dart b/lib/utils/date_formatter.dart index 9cff686146..58e59966a6 100644 --- a/lib/utils/date_formatter.dart +++ b/lib/utils/date_formatter.dart @@ -1,3 +1,4 @@ +import 'package:cake_wallet/generated/i18n.dart'; import 'package:intl/intl.dart'; import 'package:cake_wallet/di.dart'; import 'package:cake_wallet/store/settings_store.dart'; @@ -5,8 +6,7 @@ import 'package:cake_wallet/store/settings_store.dart'; class DateFormatter { static String currentLocalFormat({bool hasTime = true, bool reverse = false}) { final isUSA = getIt.get().languageCode.toLowerCase() == 'en'; - final format = - isUSA ? usaStyleFormat(hasTime, reverse) : regularStyleFormat(hasTime, reverse); + final format = isUSA ? usaStyleFormat(hasTime, reverse) : regularStyleFormat(hasTime, reverse); return format; } @@ -20,4 +20,26 @@ class DateFormatter { static String regularStyleFormat(bool hasTime, bool reverse) => hasTime ? (reverse ? 'HH:mm dd.MM.yyyy' : 'dd.MM.yyyy, HH:mm') : 'dd.MM.yyyy'; + + static String convertDateTimeToReadableString(DateTime date) { + final nowDate = DateTime.now(); + final diffDays = date.difference(nowDate).inDays; + final isToday = + nowDate.day == date.day && nowDate.month == date.month && nowDate.year == date.year; + final dateSectionDateFormat = withCurrentLocal(hasTime: false); + var title = ""; + + if (isToday) { + title = S.current.today; + } else if (diffDays == 0) { + title = S.current.yesterday; + } else if (diffDays > -7 && diffDays < 0) { + final dateFormat = DateFormat.EEEE(); + title = dateFormat.format(date); + } else { + title = dateSectionDateFormat.format(date); + } + + return title; + } } diff --git a/lib/utils/image_utill.dart b/lib/utils/image_utill.dart index a138df23a5..4389a6a013 100644 --- a/lib/utils/image_utill.dart +++ b/lib/utils/image_utill.dart @@ -11,6 +11,7 @@ class ImageUtil { if (isNetworkImage) { return isSvg ? SvgPicture.network( + key: ValueKey(imagePath), imagePath, height: _height, width: _width, @@ -23,6 +24,7 @@ class ImageUtil { ), ) : Image.network( + key: ValueKey(imagePath), imagePath, height: _height, width: _width, @@ -53,8 +55,8 @@ class ImageUtil { ); } else { return isSvg - ? SvgPicture.asset(imagePath, height: _height, width: _width) - : Image.asset(imagePath, height: _height, width: _width); + ? SvgPicture.asset(imagePath, height: _height, width: _width, key: ValueKey(imagePath)) + : Image.asset(imagePath, height: _height, width: _width, key: ValueKey(imagePath)); } } } diff --git a/lib/view_model/dashboard/formatted_item_list.dart b/lib/view_model/dashboard/formatted_item_list.dart index 1628a811e7..04464ca896 100644 --- a/lib/view_model/dashboard/formatted_item_list.dart +++ b/lib/view_model/dashboard/formatted_item_list.dart @@ -1,5 +1,6 @@ import 'package:cake_wallet/view_model/dashboard/action_list_item.dart'; import 'package:cake_wallet/view_model/dashboard/date_section_item.dart'; +import 'package:flutter/foundation.dart'; List formattedItemsList(List items) { final formattedList = []; @@ -11,7 +12,12 @@ List formattedItemsList(List items) { if (lastDate == null) { lastDate = transaction.date; - formattedList.add(DateSectionItem(transaction.date, key: transaction.key)); + formattedList.add( + DateSectionItem( + transaction.date, + key: ValueKey('date_section_item_${transaction.date.microsecondsSinceEpoch}_key'), + ), + ); formattedList.add(transaction); continue; } @@ -26,7 +32,12 @@ List formattedItemsList(List items) { } lastDate = transaction.date; - formattedList.add(DateSectionItem(transaction.date, key: transaction.key)); + formattedList.add( + DateSectionItem( + transaction.date, + key: ValueKey('date_section_item_${transaction.date.microsecondsSinceEpoch}_key'), + ), + ); formattedList.add(transaction); } diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index e4f9c3786a..c9f9c5e169 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -20,6 +20,7 @@ import 'package:cake_wallet/view_model/send/send_view_model.dart'; import 'package:collection/collection.dart'; import 'package:cw_core/transaction_direction.dart'; import 'package:cw_core/transaction_priority.dart'; +import 'package:flutter/foundation.dart'; import 'package:hive/hive.dart'; import 'package:intl/src/intl/date_format.dart'; import 'package:mobx/mobx.dart'; @@ -90,8 +91,13 @@ abstract class TransactionDetailsViewModelBase with Store { .recipientAddress; if (recipientAddress?.isNotEmpty ?? false) { - items.add(StandartListItem( - title: S.current.transaction_details_recipient_address, value: recipientAddress!)); + items.add( + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: recipientAddress!, + key: ValueKey('standard_list_item_${recipientAddress}_key'), + ), + ); } } catch (_) { // FIX-ME: Unhandled exception @@ -100,7 +106,8 @@ abstract class TransactionDetailsViewModelBase with Store { final type = wallet.type; - items.add(BlockExplorerListItem( + items.add( + BlockExplorerListItem( title: S.current.view_in_block_explorer, value: _explorerDescription(type), onTap: () async { @@ -108,13 +115,17 @@ abstract class TransactionDetailsViewModelBase with Store { final uri = Uri.parse(_explorerUrl(type, tx.txHash)); if (await canLaunchUrl(uri)) await launchUrl(uri, mode: LaunchMode.externalApplication); } catch (e) {} - })); + }, + key: ValueKey('block_explorer_list_item_${type.name}_wallet_type_key'), + ), + ); final description = transactionDescriptionBox.values.firstWhere( (val) => val.id == transactionInfo.txHash, orElse: () => TransactionDescription(id: transactionInfo.txHash)); - items.add(TextFieldListItem( + items.add( + TextFieldListItem( title: S.current.note_tap_to_change, value: description.note, onSubmitted: (value) { @@ -125,7 +136,10 @@ abstract class TransactionDetailsViewModelBase with Store { } else { transactionDescriptionBox.add(description); } - })); + }, + key: ValueKey('textfield_list_item_note_entry_key'), + ), + ); } final TransactionInfo transactionInfo; @@ -214,14 +228,38 @@ abstract class TransactionDetailsViewModelBase with Store { final addressIndex = tx.additionalInfo['addressIndex'] as int; final feeFormatted = tx.feeFormatted(); final _items = [ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), if (feeFormatted != null) - StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted), - if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!), + StandartListItem( + title: S.current.transaction_details_fee, + value: feeFormatted, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), + if (key?.isNotEmpty ?? false) + StandartListItem( + title: S.current.transaction_key, + value: key!, + key: ValueKey('standard_list_item_transaction_key'), + ), ]; if (tx.direction == TransactionDirection.incoming) { @@ -231,14 +269,21 @@ abstract class TransactionDetailsViewModelBase with Store { if (address.isNotEmpty) { isRecipientAddressShown = true; - _items.add(StandartListItem( - title: S.current.transaction_details_recipient_address, - value: address, - )); + _items.add( + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: address, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), + ); } if (label.isNotEmpty) { - _items.add(StandartListItem(title: S.current.address_label, value: label)); + _items.add(StandartListItem( + title: S.current.address_label, + value: label, + key: ValueKey('standard_list_item_address_label_key'), + )); } } catch (e) { print(e.toString()); @@ -250,14 +295,37 @@ abstract class TransactionDetailsViewModelBase with Store { void _addElectrumListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()), - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.confirmations, + value: tx.confirmations.toString(), + key: ValueKey('standard_list_item_transaction_confirmations_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), if (tx.feeFormatted()?.isNotEmpty ?? false) - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), ]; items.addAll(_items); @@ -265,30 +333,80 @@ abstract class TransactionDetailsViewModelBase with Store { void _addHavenListItems(TransactionInfo tx, DateFormat dateFormat) { items.addAll([ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), if (tx.feeFormatted()?.isNotEmpty ?? false) - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), ]); } void _addEthereumListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()), - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.confirmations, + value: tx.confirmations.toString(), + key: ValueKey('standard_list_item_transaction_confirmations_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), if (tx.feeFormatted()?.isNotEmpty ?? false) - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), if (showRecipientAddress && tx.to != null) - StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: tx.to!, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), if (tx.direction == TransactionDirection.incoming && tx.from != null) - StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!), + StandartListItem( + title: S.current.transaction_details_source_address, + value: tx.from!, + key: ValueKey('standard_list_item_transaction_details_source_address_key'), + ), ]; items.addAll(_items); @@ -296,16 +414,43 @@ abstract class TransactionDetailsViewModelBase with Store { void _addNanoListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), + StandartListItem( + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), if (showRecipientAddress && tx.to != null) - StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: tx.to!, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), if (showRecipientAddress && tx.from != null) - StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + StandartListItem( + title: S.current.transaction_details_source_address, + value: tx.from!, + key: ValueKey('standard_list_item_transaction_details_source_address_key'), + ), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.confirmed_tx, value: (tx.confirmations > 0).toString()), - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.confirmed_tx, + value: (tx.confirmations > 0).toString(), + key: ValueKey('standard_list_item_transaction_confirmed_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), ]; items.addAll(_items); @@ -313,18 +458,49 @@ abstract class TransactionDetailsViewModelBase with Store { void _addPolygonListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.confirmations, value: tx.confirmations.toString()), - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.confirmations, + value: tx.confirmations.toString(), + key: ValueKey('standard_list_item_transaction_confirmations_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), if (tx.feeFormatted()?.isNotEmpty ?? false) - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), if (showRecipientAddress && tx.to != null && tx.direction == TransactionDirection.outgoing) - StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: tx.to!, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), if (tx.direction == TransactionDirection.incoming && tx.from != null) - StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!), + StandartListItem( + title: S.current.transaction_details_source_address, + value: tx.from!, + key: ValueKey('standard_list_item_transaction_details_source_address_key'), + ), ]; items.addAll(_items); @@ -332,16 +508,39 @@ abstract class TransactionDetailsViewModelBase with Store { void _addSolanaListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), if (tx.feeFormatted()?.isNotEmpty ?? false) - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), if (showRecipientAddress && tx.to != null) - StandartListItem(title: S.current.transaction_details_recipient_address, value: tx.to!), + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: tx.to!, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), if (tx.from != null) - StandartListItem(title: S.current.transaction_details_source_address, value: tx.from!), + StandartListItem( + title: S.current.transaction_details_source_address, + value: tx.from!, + key: ValueKey('standard_list_item_transaction_details_source_address_key'), + ), ]; items.addAll(_items); @@ -359,7 +558,13 @@ abstract class TransactionDetailsViewModelBase with Store { newFee = bitcoin!.getFeeAmountForPriority( wallet, bitcoin!.getBitcoinTransactionPriorityMedium(), inputsCount, outputsCount); - RBFListItems.add(StandartListItem(title: S.current.old_fee, value: tx.feeFormatted() ?? '0.0')); + RBFListItems.add( + StandartListItem( + title: S.current.old_fee, + value: tx.feeFormatted() ?? '0.0', + key: ValueKey('standard_list_item_rbf_old_fee_key'), + ), + ); final priorities = priorityForWalletType(wallet.type); final selectedItem = priorities.indexOf(sendViewModel.transactionPriority); @@ -368,7 +573,9 @@ abstract class TransactionDetailsViewModelBase with Store { final customItemIndex = customItem != null ? priorities.indexOf(customItem) : null; final maxCustomFeeRate = sendViewModel.maxCustomFeeRate?.toDouble(); - RBFListItems.add(StandardPickerListItem( + RBFListItems.add( + StandardPickerListItem( + key: ValueKey('standard_picker_list_item_transaction_priorities_key'), title: S.current.estimated_new_fee, value: bitcoin!.formatterBitcoinAmountToString(amount: newFee) + ' ${walletTypeToCryptoCurrency(wallet.type)}', @@ -384,42 +591,73 @@ abstract class TransactionDetailsViewModelBase with Store { onItemSelected: (dynamic item, double sliderValue) { transactionPriority = item as TransactionPriority; return setNewFee(value: sliderValue, priority: transactionPriority!); - })); + }, + ), + ); if (transactionInfo.inputAddresses != null && transactionInfo.inputAddresses!.isNotEmpty) { - RBFListItems.add(StandardExpandableListItem( - title: S.current.inputs, expandableItems: transactionInfo.inputAddresses!)); + RBFListItems.add( + StandardExpandableListItem( + key: ValueKey('standard_expandable_list_item_transaction_input_addresses_key'), + title: S.current.inputs, + expandableItems: transactionInfo.inputAddresses!, + ), + ); } if (transactionInfo.outputAddresses != null && transactionInfo.outputAddresses!.isNotEmpty) { final outputAddresses = transactionInfo.outputAddresses!.map((element) { if (element.contains('OP_RETURN:') && element.length > 40) { - return element.substring(0, 40) + '...'; + return element.substring(0, 40) + '...'; } return element; }).toList(); RBFListItems.add( - StandardExpandableListItem(title: S.current.outputs, expandableItems: outputAddresses)); + StandardExpandableListItem( + title: S.current.outputs, + expandableItems: outputAddresses, + key: ValueKey('standard_expandable_list_item_transaction_output_addresses_key'), + ), + ); } } void _addTronListItems(TransactionInfo tx, DateFormat dateFormat) { final _items = [ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), if (tx.feeFormatted()?.isNotEmpty ?? false) - StandartListItem(title: S.current.transaction_details_fee, value: tx.feeFormatted()!), + StandartListItem( + title: S.current.transaction_details_fee, + value: tx.feeFormatted()!, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), if (showRecipientAddress && tx.to != null) StandartListItem( - title: S.current.transaction_details_recipient_address, - value: tron!.getTronBase58Address(tx.to!, wallet)), + title: S.current.transaction_details_recipient_address, + value: tron!.getTronBase58Address(tx.to!, wallet), + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), if (tx.from != null) StandartListItem( - title: S.current.transaction_details_source_address, - value: tron!.getTronBase58Address(tx.from!, wallet)), + title: S.current.transaction_details_source_address, + value: tron!.getTronBase58Address(tx.from!, wallet), + key: ValueKey('standard_list_item_transaction_details_source_address_key'), + ), ]; items.addAll(_items); @@ -451,7 +689,7 @@ abstract class TransactionDetailsViewModelBase with Store { return bitcoin!.formatterBitcoinAmountToString(amount: newFee); } - void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo, newFee,); + void replaceByFee(String newFee) => sendViewModel.replaceByFee(transactionInfo, newFee); @computed String get pendingTransactionFiatAmountValueFormatted => sendViewModel.isFiatDisabled @@ -469,14 +707,38 @@ abstract class TransactionDetailsViewModelBase with Store { final addressIndex = tx.additionalInfo['addressIndex'] as int; final feeFormatted = tx.feeFormatted(); final _items = [ - StandartListItem(title: S.current.transaction_details_transaction_id, value: tx.txHash), StandartListItem( - title: S.current.transaction_details_date, value: dateFormat.format(tx.date)), - StandartListItem(title: S.current.transaction_details_height, value: '${tx.height}'), - StandartListItem(title: S.current.transaction_details_amount, value: tx.amountFormatted()), + title: S.current.transaction_details_transaction_id, + value: tx.txHash, + key: ValueKey('standard_list_item_transaction_details_id_key'), + ), + StandartListItem( + title: S.current.transaction_details_date, + value: dateFormat.format(tx.date), + key: ValueKey('standard_list_item_transaction_details_date_key'), + ), + StandartListItem( + title: S.current.transaction_details_height, + value: '${tx.height}', + key: ValueKey('standard_list_item_transaction_details_height_key'), + ), + StandartListItem( + title: S.current.transaction_details_amount, + value: tx.amountFormatted(), + key: ValueKey('standard_list_item_transaction_details_amount_key'), + ), if (feeFormatted != null) - StandartListItem(title: S.current.transaction_details_fee, value: feeFormatted), - if (key?.isNotEmpty ?? false) StandartListItem(title: S.current.transaction_key, value: key!), + StandartListItem( + title: S.current.transaction_details_fee, + value: feeFormatted, + key: ValueKey('standard_list_item_transaction_details_fee_key'), + ), + if (key?.isNotEmpty ?? false) + StandartListItem( + title: S.current.transaction_key, + value: key!, + key: ValueKey('standard_list_item_transaction_key'), + ), ]; if (tx.direction == TransactionDirection.incoming) { @@ -486,14 +748,23 @@ abstract class TransactionDetailsViewModelBase with Store { if (address.isNotEmpty) { isRecipientAddressShown = true; - _items.add(StandartListItem( - title: S.current.transaction_details_recipient_address, - value: address, - )); + _items.add( + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: address, + key: ValueKey('standard_list_item_transaction_details_recipient_address_key'), + ), + ); } if (label.isNotEmpty) { - _items.add(StandartListItem(title: S.current.address_label, value: label)); + _items.add( + StandartListItem( + title: S.current.address_label, + value: label, + key: ValueKey('standard_list_item_address_label_key'), + ), + ); } } catch (e) { print(e.toString()); From d0e0ddd1e31c993a120b8f5fd83716678835fe2a Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Wed, 18 Sep 2024 08:26:53 +0100 Subject: [PATCH 63/69] test: Updating send page robot and also syncing branch with main --- integration_test/robots/send_page_robot.dart | 21 ++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index b2ecc4c12e..971556620c 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -140,21 +140,26 @@ class SendPageRobot { } Future _waitForSendTransactionCompletion() async { + await tester.pump(); final Completer completer = Completer(); // Loop to wait for the async operation to complete while (true) { await Future.delayed(Duration(seconds: 1)); - tester.printToConsole('Before in auth'); + tester.printToConsole('Before _handleAuth'); await _handleAuthPage(); - tester.printToConsole('After in auth'); + tester.printToConsole('After _handleAuth'); + + await tester.pump(); final sendPage = tester.widget(find.byType(SendPage)); final state = sendPage.sendViewModel.state; + await tester.pump(); + bool isDone = state is ExecutedSuccessfullyState; bool isFailed = state is FailureState; @@ -180,10 +185,18 @@ class SendPageRobot { } Future _handleAuthPage() async { + tester.printToConsole('Inside _handleAuth'); + await tester.pump(); + tester.printToConsole('starting auth checks'); + final authPage = authPageRobot.onAuthPage(); + + tester.printToConsole('hasAuth:$authPage'); + if (authPage) { - tester.printToConsole('Auth'); await tester.pump(); + tester.printToConsole('Starting inner _handleAuth loop checks'); + try { await authPageRobot.enterPinCode(CommonTestConstants.pin, false); tester.printToConsole('Auth done'); @@ -350,4 +363,4 @@ class SendPageRobot { Future _onIgnoreButtonOnSentDialogPressed() async { await commonTestCases.tapItemByKey('send_page_sent_dialog_ignore_button_key'); } -} +} \ No newline at end of file From 761b746e3f0687ccc1086658a923dc9b216559a9 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Fri, 27 Sep 2024 23:55:48 +0100 Subject: [PATCH 64/69] test: Modifying tests to flow with wallet grouping implementation --- .../components/common_test_constants.dart | 2 +- .../components/common_test_flows.dart | 23 +++++++++++-- integration_test/robots/send_page_robot.dart | 3 ++ .../wallet_group_description_page_robot.dart | 32 +++++++++++++++++++ .../wallet_group_description_page.dart | 2 ++ tool/utils/secret_key.dart | 2 ++ 6 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 integration_test/robots/wallet_group_description_page_robot.dart diff --git a/integration_test/components/common_test_constants.dart b/integration_test/components/common_test_constants.dart index d8381973e3..302d521893 100644 --- a/integration_test/components/common_test_constants.dart +++ b/integration_test/components/common_test_constants.dart @@ -9,5 +9,5 @@ class CommonTestConstants { static final String testWalletName = 'Integrated Testing Wallet'; static final CryptoCurrency testReceiveCurrency = CryptoCurrency.sol; static final CryptoCurrency testDepositCurrency = CryptoCurrency.usdtSol; - static final String testWalletAddress = 'An2Y2fsUYKfYvN1zF89GAqR1e6GUMBg3qA83Y5ZWDf8L'; + static final String testWalletAddress = '5v9gTW1yWPffhnbNKuvtL2frevAf4HpBMw8oYnfqUjhm'; } diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index 3f312b44f0..f85a3ae09c 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -1,4 +1,5 @@ import 'package:cake_wallet/entities/seed_type.dart'; +import 'package:cake_wallet/reactions/bip39_wallet_utils.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter_test/flutter_test.dart'; @@ -13,6 +14,7 @@ import '../robots/pre_seed_page_robot.dart'; import '../robots/restore_from_seed_or_key_robot.dart'; import '../robots/restore_options_page_robot.dart'; import '../robots/setup_pin_code_robot.dart'; +import '../robots/wallet_group_description_page_robot.dart'; import '../robots/wallet_list_page_robot.dart'; import '../robots/wallet_seed_page_robot.dart'; import '../robots/welcome_page_robot.dart'; @@ -34,7 +36,8 @@ class CommonTestFlows { _walletListPageRobot = WalletListPageRobot(_tester), _newWalletTypePageRobot = NewWalletTypePageRobot(_tester), _restoreOptionsPageRobot = RestoreOptionsPageRobot(_tester), - _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester); + _restoreFromSeedOrKeysPageRobot = RestoreFromSeedOrKeysPageRobot(_tester), + _walletGroupDescriptionPageRobot = WalletGroupDescriptionPageRobot(_tester); final WidgetTester _tester; final CommonTestCases _commonTestCases; @@ -50,6 +53,7 @@ class CommonTestFlows { final NewWalletTypePageRobot _newWalletTypePageRobot; final RestoreOptionsPageRobot _restoreOptionsPageRobot; final RestoreFromSeedOrKeysPageRobot _restoreFromSeedOrKeysPageRobot; + final WalletGroupDescriptionPageRobot _walletGroupDescriptionPageRobot; //* ========== Handles flow to start the app afresh and accept disclaimer ============= Future startAppFlow(Key key) async { @@ -115,6 +119,9 @@ class CommonTestFlows { await _selectWalletTypeForWallet(walletTypeToCreate); await _commonTestCases.defaultSleepTime(); + // ---- Wallet Group/New Seed Implementation Comes here + await _walletGroupDescriptionPageFlow(true, walletTypeToCreate); + await _generateNewWalletDetails(); await _confirmPreSeedInfo(); @@ -123,6 +130,18 @@ class CommonTestFlows { await _commonTestCases.defaultSleepTime(); } + Future _walletGroupDescriptionPageFlow(bool isNewSeed, WalletType walletType) async { + if (!isBIP39Wallet(walletType)) return; + + await _walletGroupDescriptionPageRobot.isWalletGroupDescriptionPage(); + + if (isNewSeed) { + await _walletGroupDescriptionPageRobot.navigateToCreateNewSeedPage(); + } else { + await _walletGroupDescriptionPageRobot.navigateToChooseWalletGroup(); + } + } + //* ========== Handles restore wallet flow from wallet list/menu =============== Future restoreWalletFromWalletMenu(WalletType walletType, String walletSeed) async { _tester.printToConsole('Restoring ${walletType.name} Wallet'); @@ -230,7 +249,7 @@ class CommonTestFlows { .chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType.legacy); // Using a constant value of 2831400 for the blockheight as its the restore blockheight for our testing wallet - await _restoreFromSeedOrKeysPageRobot.enterBlockHeightForWalletRestore('2831400'); + await _restoreFromSeedOrKeysPageRobot.enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight); } await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); diff --git a/integration_test/robots/send_page_robot.dart b/integration_test/robots/send_page_robot.dart index c9e6b15daf..20cef948de 100644 --- a/integration_test/robots/send_page_robot.dart +++ b/integration_test/robots/send_page_robot.dart @@ -213,6 +213,7 @@ class SendPageRobot { } Future handleSendResult() async { + await tester.pump(); tester.printToConsole('Inside handle function'); bool hasError = false; @@ -287,6 +288,8 @@ class SendPageRobot { // Loop to wait for the operation to commit transaction await _waitForCommitTransactionCompletion(); + await tester.pump(); + await commonTestCases.defaultSleepTime(seconds: 4); } else { await commonTestCases.defaultSleepTime(); diff --git a/integration_test/robots/wallet_group_description_page_robot.dart b/integration_test/robots/wallet_group_description_page_robot.dart new file mode 100644 index 0000000000..57500dc3c3 --- /dev/null +++ b/integration_test/robots/wallet_group_description_page_robot.dart @@ -0,0 +1,32 @@ +import 'package:cake_wallet/generated/i18n.dart'; +import 'package:cake_wallet/src/screens/new_wallet/wallet_group_description_page.dart'; +import 'package:flutter_test/flutter_test.dart'; + +import '../components/common_test_cases.dart'; + +class WalletGroupDescriptionPageRobot { + WalletGroupDescriptionPageRobot(this.tester) : commonTestCases = CommonTestCases(tester); + + final WidgetTester tester; + final CommonTestCases commonTestCases; + + Future isWalletGroupDescriptionPage() async { + await commonTestCases.isSpecificPage(); + } + + void hasTitle() { + commonTestCases.hasText(S.current.wallet_group); + } + + Future navigateToCreateNewSeedPage() async { + await commonTestCases.tapItemByKey( + 'wallet_group_description_page_create_new_seed_button_key', + ); + } + + Future navigateToChooseWalletGroup() async { + await commonTestCases.tapItemByKey( + 'wallet_group_description_page_choose_wallet_group_button_key', + ); + } +} diff --git a/lib/src/screens/new_wallet/wallet_group_description_page.dart b/lib/src/screens/new_wallet/wallet_group_description_page.dart index eb3501bed6..d9ecfe0ea5 100644 --- a/lib/src/screens/new_wallet/wallet_group_description_page.dart +++ b/lib/src/screens/new_wallet/wallet_group_description_page.dart @@ -64,6 +64,7 @@ class WalletGroupDescriptionPage extends BasePage { ), ), PrimaryButton( + key: ValueKey('wallet_group_description_page_create_new_seed_button_key'), onPressed: () => Navigator.of(context).pushNamed( Routes.newWallet, arguments: NewWalletArguments(type: selectedWalletType), @@ -74,6 +75,7 @@ class WalletGroupDescriptionPage extends BasePage { ), SizedBox(height: 12), PrimaryButton( + key: ValueKey('wallet_group_description_page_choose_wallet_group_button_key'), onPressed: () => Navigator.of(context).pushNamed( Routes.walletGroupsDisplayPage, arguments: selectedWalletType, diff --git a/tool/utils/secret_key.dart b/tool/utils/secret_key.dart index 36e6b80f5c..88cbbfce38 100644 --- a/tool/utils/secret_key.dart +++ b/tool/utils/secret_key.dart @@ -45,6 +45,7 @@ class SecretKey { SecretKey('CSRFToken', () => ''), SecretKey('authorization', () => ''), SecretKey('moneroTestWalletSeeds', () => ''), + SecretKey('moneroLegacyTestWalletSeeds ', () => ''), SecretKey('bitcoinTestWalletSeeds', () => ''), SecretKey('ethereumTestWalletSeeds', () => ''), SecretKey('litecoinTestWalletSeeds', () => ''), @@ -71,6 +72,7 @@ class SecretKey { SecretKey('letsExchangeAffiliateId', () => ''), SecretKey('stealthExBearerToken', () => ''), SecretKey('stealthExAdditionalFeePercent', () => ''), + SecretKey('moneroTestWalletBlockHeight', () => ''), ]; static final evmChainsSecrets = [ From cc00fda5a32b45c836224dd0b6986fcda5c9561d Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Sat, 28 Sep 2024 03:29:51 +0100 Subject: [PATCH 65/69] fix: Issue with transaction history test --- integration_test/robots/transactions_page_robot.dart | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/integration_test/robots/transactions_page_robot.dart b/integration_test/robots/transactions_page_robot.dart index 3625727b74..40a49928ff 100644 --- a/integration_test/robots/transactions_page_robot.dart +++ b/integration_test/robots/transactions_page_robot.dart @@ -13,7 +13,6 @@ import 'package:cake_wallet/view_model/dashboard/transaction_list_item.dart'; import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/transaction_direction.dart'; -import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:intl/intl.dart'; @@ -170,12 +169,9 @@ class TransactionsPageRobot { ); //* ======Confirm it displays the properly formatted title=========== - final transactionType = dashboardViewModel.type == WalletType.ethereum && - item.transaction.evmSignatureName == 'approval' - ? ' (${item.transaction.evmSignatureName})' - : ''; + final transactionType = dashboardViewModel.getTransactionType(item.transaction); - final title = item.formattedTitle + item.formattedStatus + ' $transactionType'; + final title = item.formattedTitle + item.formattedStatus + transactionType; commonTestCases.findWidgetViaDescendant( of: find.byKey(ValueKey(keyId)), From 67ac2029ede3637bb0537b2831db4edf50043570 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 3 Oct 2024 14:25:56 +0100 Subject: [PATCH 66/69] fix: Modifications to the PR and add automated confirmation for checking that all wallet types are restored or created correctly --- .../components/common_test_flows.dart | 22 +++++- .../test_suites/create_wallet_flow_test.dart | 4 +- ...estore_wallet_through_seeds_flow_test.dart | 4 +- ios/Podfile.lock | 24 +++---- .../dashboard/pages/transactions_page.dart | 72 ++++++++++--------- lib/src/widgets/setting_actions.dart | 1 + 6 files changed, 78 insertions(+), 49 deletions(-) diff --git a/integration_test/components/common_test_flows.dart b/integration_test/components/common_test_flows.dart index f85a3ae09c..82f714da0f 100644 --- a/integration_test/components/common_test_flows.dart +++ b/integration_test/components/common_test_flows.dart @@ -1,7 +1,8 @@ import 'package:cake_wallet/entities/seed_type.dart'; import 'package:cake_wallet/reactions/bip39_wallet_utils.dart'; +import 'package:cake_wallet/wallet_types.g.dart'; import 'package:cw_core/wallet_type.dart'; -import 'package:flutter/foundation.dart'; +import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:cake_wallet/main.dart' as app; @@ -110,6 +111,22 @@ class CommonTestFlows { await _dashboardPageRobot.dashboardMenuWidgetRobot.navigateToWalletMenu(); } + void confirmAllAvailableWalletTypeIconsDisplayCorrectly() { + for (var walletType in availableWalletTypes) { + final imageUrl = walletTypeToCryptoCurrency(walletType).iconPath; + + final walletIconFinder = find.image( + Image.asset( + imageUrl!, + width: 32, + height: 32, + ).image, + ); + + expect(walletIconFinder, findsAny); + } + } + //* ========== Handles creating new wallet flow from wallet list/menu =============== Future createNewWalletFromWalletMenu(WalletType walletTypeToCreate) async { _tester.printToConsole('Creating ${walletTypeToCreate.name} Wallet'); @@ -249,7 +266,8 @@ class CommonTestFlows { .chooseSeedTypeForMoneroOrWowneroWallets(MoneroSeedType.legacy); // Using a constant value of 2831400 for the blockheight as its the restore blockheight for our testing wallet - await _restoreFromSeedOrKeysPageRobot.enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight); + await _restoreFromSeedOrKeysPageRobot + .enterBlockHeightForWalletRestore(secrets.moneroTestWalletBlockHeight); } await _restoreFromSeedOrKeysPageRobot.onRestoreWalletButtonPressed(); diff --git a/integration_test/test_suites/create_wallet_flow_test.dart b/integration_test/test_suites/create_wallet_flow_test.dart index 71bbab386c..2a50dbbe8e 100644 --- a/integration_test/test_suites/create_wallet_flow_test.dart +++ b/integration_test/test_suites/create_wallet_flow_test.dart @@ -46,9 +46,11 @@ void main() { await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType); } - // Go to the wallet menu, provides a visual confirmation that all the wallets were correctly restored + // Goes to the wallet menu and provides a confirmation that all the wallets were correctly restored await commonTestFlows.switchToWalletMenuFromDashboardPage(); + commonTestFlows.confirmAllAvailableWalletTypeIconsDisplayCorrectly(); + await Future.delayed(Duration(seconds: 5)); }, ); diff --git a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart index 28525f5569..a810aa7221 100644 --- a/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart +++ b/integration_test/test_suites/restore_wallet_through_seeds_flow_test.dart @@ -52,9 +52,11 @@ void main() { await dashboardPageRobot.confirmWalletTypeIsDisplayedCorrectly(walletType); } - // Go to the wallet menu, provides a visual confirmation that all the wallets were correctly restored + // Goes to the wallet menu and provides a visual confirmation that all the wallets were correctly restored await commonTestFlows.switchToWalletMenuFromDashboardPage(); + commonTestFlows.confirmAllAvailableWalletTypeIconsDisplayCorrectly(); + await Future.delayed(Duration(seconds: 5)); }, ); diff --git a/ios/Podfile.lock b/ios/Podfile.lock index e574aafc13..270b89cfe1 100644 --- a/ios/Podfile.lock +++ b/ios/Podfile.lock @@ -6,8 +6,9 @@ PODS: - connectivity_plus (0.0.1): - Flutter - ReachabilitySwift - - CryptoSwift (1.8.2) + - CryptoSwift (1.8.3) - cw_mweb (0.0.1): + - Flutter - device_display_brightness (0.0.1): - Flutter - device_info_plus (0.0.1): @@ -78,15 +79,15 @@ PODS: - FlutterMacOS - permission_handler_apple (9.1.1): - Flutter - - Protobuf (3.27.2) + - Protobuf (3.28.2) - ReachabilitySwift (5.2.3) - reactive_ble_mobile (0.0.1): - Flutter - Protobuf (~> 3.5) - SwiftProtobuf (~> 1.0) - - SDWebImage (5.19.4): - - SDWebImage/Core (= 5.19.4) - - SDWebImage/Core (5.19.4) + - SDWebImage (5.19.7): + - SDWebImage/Core (= 5.19.7) + - SDWebImage/Core (5.19.7) - sensitive_clipboard (0.0.1): - Flutter - share_plus (0.0.1): @@ -96,7 +97,7 @@ PODS: - FlutterMacOS - sp_scanner (0.0.1): - Flutter - - SwiftProtobuf (1.26.0) + - SwiftProtobuf (1.28.2) - SwiftyGif (5.4.5) - Toast (4.1.1) - uni_links (0.0.1): @@ -125,7 +126,6 @@ DEPENDENCIES: - fluttertoast (from `.symlinks/plugins/fluttertoast/ios`) - in_app_review (from `.symlinks/plugins/in_app_review/ios`) - integration_test (from `.symlinks/plugins/integration_test/ios`) - - package_info (from `.symlinks/plugins/package_info/ios`) - package_info_plus (from `.symlinks/plugins/package_info_plus/ios`) - path_provider_foundation (from `.symlinks/plugins/path_provider_foundation/darwin`) - permission_handler_apple (from `.symlinks/plugins/permission_handler_apple/ios`) @@ -184,8 +184,6 @@ EXTERNAL SOURCES: :path: ".symlinks/plugins/in_app_review/ios" integration_test: :path: ".symlinks/plugins/integration_test/ios" - package_info: - :path: ".symlinks/plugins/package_info/ios" package_info_plus: :path: ".symlinks/plugins/package_info_plus/ios" path_provider_foundation: @@ -214,7 +212,7 @@ EXTERNAL SOURCES: SPEC CHECKSUMS: barcode_scan2: 0af2bb63c81b4565aab6cd78278e4c0fa136dbb0 connectivity_plus: bf0076dd84a130856aa636df1c71ccaff908fa1d - CryptoSwift: c63a805d8bb5e5538e88af4e44bb537776af11ea + CryptoSwift: 967f37cea5a3294d9cce358f78861652155be483 cw_mweb: 87af74f9659fed0c1a2cbfb44413f1070e79e3ae device_display_brightness: 1510e72c567a1f6ce6ffe393dcd9afd1426034f7 device_info_plus: c6fb39579d0f423935b0c9ce7ee2f44b71b9fce6 @@ -235,15 +233,15 @@ SPEC CHECKSUMS: package_info_plus: 58f0028419748fad15bf008b270aaa8e54380b1c path_provider_foundation: 2b6b4c569c0fb62ec74538f866245ac84301af46 permission_handler_apple: e76247795d700c14ea09e3a2d8855d41ee80a2e6 - Protobuf: fb2c13674723f76ff6eede14f78847a776455fa2 + Protobuf: 28c89b24435762f60244e691544ed80f50d82701 ReachabilitySwift: 7f151ff156cea1481a8411701195ac6a984f4979 reactive_ble_mobile: 9ce6723d37ccf701dbffd202d487f23f5de03b4c - SDWebImage: 066c47b573f408f18caa467d71deace7c0f8280d + SDWebImage: 8a6b7b160b4d710e2a22b6900e25301075c34cb3 sensitive_clipboard: d4866e5d176581536c27bb1618642ee83adca986 share_plus: 8875f4f2500512ea181eef553c3e27dba5135aad shared_preferences_foundation: fcdcbc04712aee1108ac7fda236f363274528f78 sp_scanner: eaa617fa827396b967116b7f1f43549ca62e9a12 - SwiftProtobuf: 5e8349171e7c2f88f5b9e683cb3cb79d1dc780b3 + SwiftProtobuf: 4dbaffec76a39a8dc5da23b40af1a5dc01a4c02d SwiftyGif: 706c60cf65fa2bc5ee0313beece843c8eb8194d4 Toast: 1f5ea13423a1e6674c4abdac5be53587ae481c4e uni_links: d97da20c7701486ba192624d99bffaaffcfc298a diff --git a/lib/src/screens/dashboard/pages/transactions_page.dart b/lib/src/screens/dashboard/pages/transactions_page.dart index d50747d35d..0db9ac35b6 100644 --- a/lib/src/screens/dashboard/pages/transactions_page.dart +++ b/lib/src/screens/dashboard/pages/transactions_page.dart @@ -1,7 +1,14 @@ import 'package:cake_wallet/bitcoin/bitcoin.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/anonpay_transaction_row.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/order_row.dart'; +import 'package:cake_wallet/src/screens/dashboard/widgets/trade_row.dart'; import 'package:cake_wallet/themes/extensions/placeholder_theme.dart'; import 'package:cake_wallet/src/widgets/dashboard_card_widget.dart'; import 'package:cake_wallet/utils/responsive_layout_util.dart'; +import 'package:cake_wallet/view_model/dashboard/anonpay_transaction_list_item.dart'; +import 'package:cake_wallet/view_model/dashboard/order_list_item.dart'; +import 'package:cake_wallet/view_model/dashboard/trade_list_item.dart'; +import 'package:cw_core/crypto_currency.dart'; import 'package:cw_core/sync_status.dart'; import 'package:cw_core/wallet_type.dart'; import 'package:flutter/material.dart'; @@ -83,40 +90,41 @@ class TransactionsPage extends StatelessWidget { return Container(); } - final transaction = item.transaction; - final transactionType = dashboardViewModel.getTransactionType(transaction); + final transaction = item.transaction; + final transactionType = + dashboardViewModel.getTransactionType(transaction); - List tags = []; - if (dashboardViewModel.type == WalletType.bitcoin) { - if (bitcoin!.txIsReceivedSilentPayment(transaction)) { - tags.add(S.of(context).silent_payment); - } - } - if (dashboardViewModel.type == WalletType.litecoin) { - if (bitcoin!.txIsMweb(transaction)) { - tags.add("MWEB"); + List tags = []; + if (dashboardViewModel.type == WalletType.bitcoin) { + if (bitcoin!.txIsReceivedSilentPayment(transaction)) { + tags.add(S.of(context).silent_payment); + } + } + if (dashboardViewModel.type == WalletType.litecoin) { + if (bitcoin!.txIsMweb(transaction)) { + tags.add("MWEB"); + } + } + + return Observer( + builder: (_) => TransactionRow( + key: item.key, + onTap: () => Navigator.of(context) + .pushNamed(Routes.transactionDetails, arguments: transaction), + direction: transaction.direction, + formattedDate: DateFormat('HH:mm').format(transaction.date), + formattedAmount: item.formattedCryptoAmount, + formattedFiatAmount: + dashboardViewModel.balanceViewModel.isFiatDisabled + ? '' + : item.formattedFiatAmount, + isPending: transaction.isPending, + title: + item.formattedTitle + item.formattedStatus + transactionType, + tags: tags, + ), + ); } - } - - return Observer( - builder: (_) => TransactionRow( - onTap: () => Navigator.of(context) - .pushNamed(Routes.transactionDetails, arguments: transaction), - direction: transaction.direction, - formattedDate: DateFormat('HH:mm').format(transaction.date), - formattedAmount: item.formattedCryptoAmount, - formattedFiatAmount: - dashboardViewModel.balanceViewModel.isFiatDisabled - ? '' - : item.formattedFiatAmount, - isPending: transaction.isPending, - title: - item.formattedTitle + item.formattedStatus + transactionType, - tags: tags, - ), - ); - } - } if (item is AnonpayTransactionListItem) { final transactionInfo = item.transaction; diff --git a/lib/src/widgets/setting_actions.dart b/lib/src/widgets/setting_actions.dart index fe00ecd5e6..c6dda06c4c 100644 --- a/lib/src/widgets/setting_actions.dart +++ b/lib/src/widgets/setting_actions.dart @@ -52,6 +52,7 @@ class SettingActions { ); static SettingActions litecoinMwebSettingAction = SettingActions._( + key: ValueKey('dashboard_page_menu_widget_litecoin_mweb_settings_button_key'), name: (context) => S.current.litecoin_mweb_settings, image: 'assets/images/bitcoin_menu.png', onTap: (BuildContext context) { From d160ee2452b564debfcede6a0ef2c648dfef6971 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Tue, 8 Oct 2024 16:46:24 +0100 Subject: [PATCH 67/69] test: Attempting automation for testing --- .../workflows/automated_integration_test.yml | 255 ++++++++++++++++++ 1 file changed, 255 insertions(+) create mode 100644 .github/workflows/automated_integration_test.yml diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml new file mode 100644 index 0000000000..cae2b83321 --- /dev/null +++ b/.github/workflows/automated_integration_test.yml @@ -0,0 +1,255 @@ +name: Automated Integration Tests + +on: + pull_request: + branches: [main] + workflow_dispatch: + inputs: + branch: + description: "Branch name to build" + required: true + default: "main" + +jobs: + Automated_integration_test: + runs-on: ubuntu-20.04 + strategy: + fail-fast: false + matrix: + api-level: [29] + arch: [x86, x86_64] + env: + STORE_PASS: test@cake_wallet + KEY_PASS: test@cake_wallet + PR_NUMBER: ${{ github.event.number }} + + steps: + - name: is pr + if: github.event_name == 'pull_request' + run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV + + - name: is not pr + if: github.event_name != 'pull_request' + run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV + + - name: Free Disk Space (Ubuntu) + uses: insightsengineering/disk-space-reclaimer@v1 + with: + tools-cache: true + android: false + dotnet: true + haskell: true + large-packages: true + swap-storage: true + docker-images: true + + - uses: actions/checkout@v2 + - uses: actions/setup-java@v2 + with: + distribution: "temurin" + java-version: "17" + - name: Configure placeholder git details + run: | + git config --global user.email "CI@cakewallet.com" + git config --global user.name "Cake Github Actions" + - name: Flutter action + uses: subosito/flutter-action@v1 + with: + flutter-version: "3.19.6" + channel: stable + + - name: Install package dependencies + run: | + sudo apt update + sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang + + - name: Execute Build and Setup Commands + run: | + sudo mkdir -p /opt/android + sudo chown $USER /opt/android + cd /opt/android + -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh + cargo install cargo-ndk + git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} + cd cake_wallet/scripts/android/ + ./install_ndk.sh + source ./app_env.sh cakewallet + chmod +x pubspec_gen.sh + ./app_config.sh + + - name: Cache Externals + id: cache-externals + uses: actions/cache@v3 + with: + path: | + /opt/android/cake_wallet/cw_haven/android/.cxx + /opt/android/cake_wallet/scripts/monero_c/release + key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} + + - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} + name: Generate Externals + run: | + cd /opt/android/cake_wallet/scripts/android/ + source ./app_env.sh cakewallet + ./build_monero_all.sh + + - name: Install Flutter dependencies + run: | + cd /opt/android/cake_wallet + flutter pub get + + + - name: Install go and gomobile + run: | + # install go > 1.23: + wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz + sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz + export PATH=$PATH:/usr/local/go/bin + export PATH=$PATH:~/go/bin + go install golang.org/x/mobile/cmd/gomobile@latest + gomobile init + + - name: Build mwebd + run: | + # paths are reset after each step, so we need to set them again: + export PATH=$PATH:/usr/local/go/bin + export PATH=$PATH:~/go/bin + cd /opt/android/cake_wallet/scripts/android/ + ./build_mwebd.sh --dont-install + + - name: Generate KeyStore + run: | + cd /opt/android/cake_wallet/android/app + keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS + + - name: Generate key properties + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS + + - name: Generate localization + run: | + cd /opt/android/cake_wallet + flutter packages pub run tool/generate_localization.dart + + - name: Build generated code + run: | + cd /opt/android/cake_wallet + ./model_generator.sh + + - name: Add secrets + run: | + cd /opt/android/cake_wallet + touch lib/.secrets.g.dart + touch cw_evm/lib/.secrets.g.dart + touch cw_solana/lib/.secrets.g.dart + touch cw_core/lib/.secrets.g.dart + touch cw_nano/lib/.secrets.g.dart + touch cw_tron/lib/.secrets.g.dart + echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart + echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart + echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart + echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart + echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart + echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart + echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart + echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart + echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart + echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart + echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart + echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart + echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart + echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart + echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart + echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart + echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart + echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart + echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart + echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart + echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart + echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart + echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart + echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart + echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart + echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart + echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart + echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart + echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart + echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart + echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart + echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart + echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart + echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart + + - name: Rename app + run: | + echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties + + - name: Build + run: | + cd /opt/android/cake_wallet + flutter build apk --release --split-per-abi + + - name: 🦾 Enable KVM + run: | + echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules + sudo udevadm control --reload-rules + sudo udevadm trigger --name-match=kvm + + - name: 🦾 Cache gradle + uses: gradle/actions/setup-gradle@v3 + + - name: 🦾 Cache AVD + uses: actions/cache@v4 + id: avd-cache + with: + path: | + ~/.android/avd/* + ~/.android/adb* + key: avd-${{ matrix.api-level }} + + - name: 🦾 Create AVD and generate snapshot for caching + if: steps.avd-cache.outputs.cache-hit != 'true' + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + # arch: ${{ matrix.arch }} + emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: false + script: echo "Generated AVD snapshot for caching." + + - name: 🚀 Integration tests on Android Emualator + uses: reactivecircus/android-emulator-runner@v2 + with: + api-level: ${{ matrix.api-level }} + force-avd-creation: false + # arch: ${{ matrix.arch }} + emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none + disable-animations: true + script: | + cd /opt/android/cake_wallet + flutter pub get + flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/ + if [ $? -ne 0 ]; then + echo "Tests failed" + exit 1 + fi From 182349580e8c6fdaf6e218e0d13bf7e50f209a02 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 7 Nov 2024 07:37:41 +0100 Subject: [PATCH 68/69] fix: Issue from merge conflicts --- .../transaction_details_view_model.dart | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/lib/view_model/transaction_details_view_model.dart b/lib/view_model/transaction_details_view_model.dart index 9bb4ca5926..a189ebe6c9 100644 --- a/lib/view_model/transaction_details_view_model.dart +++ b/lib/view_model/transaction_details_view_model.dart @@ -53,7 +53,7 @@ abstract class TransactionDetailsViewModelBase with Store { break; case WalletType.bitcoin: _addElectrumListItems(tx, dateFormat); - if(!canReplaceByFee)_checkForRBF(tx); + if (!canReplaceByFee) _checkForRBF(tx); break; case WalletType.litecoin: case WalletType.bitcoinCash: @@ -84,8 +84,7 @@ abstract class TransactionDetailsViewModelBase with Store { break; } - final descriptionKey = - '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; + final descriptionKey = '${transactionInfo.txHash}_${wallet.walletAddresses.primaryAddress}'; final description = transactionDescriptionBox.values.firstWhere( (val) => val.id == descriptionKey || val.id == transactionInfo.txHash, orElse: () => TransactionDescription(id: descriptionKey)); @@ -94,10 +93,12 @@ abstract class TransactionDetailsViewModelBase with Store { final recipientAddress = description.recipientAddress; if (recipientAddress?.isNotEmpty ?? false) { - items.add(StandartListItem( - title: S.current.transaction_details_recipient_address, - value: recipientAddress!), - key: ValueKey('standard_list_item_${recipientAddress}_key'), + items.add( + StandartListItem( + title: S.current.transaction_details_recipient_address, + value: recipientAddress!, + key: ValueKey('standard_list_item_${recipientAddress}_key'), + ), ); } } @@ -118,7 +119,8 @@ abstract class TransactionDetailsViewModelBase with Store { ), ); - items.add(TextFieldListItem( + items.add( + TextFieldListItem( title: S.current.note_tap_to_change, value: description.note, onSubmitted: (value) { From 83612aebc6f680ec6cf34883a8d7884fab6d4a94 Mon Sep 17 00:00:00 2001 From: Blazebrain Date: Thu, 7 Nov 2024 08:00:27 +0100 Subject: [PATCH 69/69] test: Remove automation of test in this PR --- .../workflows/automated_integration_test.yml | 255 ------------------ 1 file changed, 255 deletions(-) delete mode 100644 .github/workflows/automated_integration_test.yml diff --git a/.github/workflows/automated_integration_test.yml b/.github/workflows/automated_integration_test.yml deleted file mode 100644 index cae2b83321..0000000000 --- a/.github/workflows/automated_integration_test.yml +++ /dev/null @@ -1,255 +0,0 @@ -name: Automated Integration Tests - -on: - pull_request: - branches: [main] - workflow_dispatch: - inputs: - branch: - description: "Branch name to build" - required: true - default: "main" - -jobs: - Automated_integration_test: - runs-on: ubuntu-20.04 - strategy: - fail-fast: false - matrix: - api-level: [29] - arch: [x86, x86_64] - env: - STORE_PASS: test@cake_wallet - KEY_PASS: test@cake_wallet - PR_NUMBER: ${{ github.event.number }} - - steps: - - name: is pr - if: github.event_name == 'pull_request' - run: echo "BRANCH_NAME=${GITHUB_HEAD_REF}" >> $GITHUB_ENV - - - name: is not pr - if: github.event_name != 'pull_request' - run: echo "BRANCH_NAME=${{ github.event.inputs.branch }}" >> $GITHUB_ENV - - - name: Free Disk Space (Ubuntu) - uses: insightsengineering/disk-space-reclaimer@v1 - with: - tools-cache: true - android: false - dotnet: true - haskell: true - large-packages: true - swap-storage: true - docker-images: true - - - uses: actions/checkout@v2 - - uses: actions/setup-java@v2 - with: - distribution: "temurin" - java-version: "17" - - name: Configure placeholder git details - run: | - git config --global user.email "CI@cakewallet.com" - git config --global user.name "Cake Github Actions" - - name: Flutter action - uses: subosito/flutter-action@v1 - with: - flutter-version: "3.19.6" - channel: stable - - - name: Install package dependencies - run: | - sudo apt update - sudo apt-get install -y curl unzip automake build-essential file pkg-config git python libtool libtinfo5 cmake clang - - - name: Execute Build and Setup Commands - run: | - sudo mkdir -p /opt/android - sudo chown $USER /opt/android - cd /opt/android - -y curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh - cargo install cargo-ndk - git clone https://github.com/cake-tech/cake_wallet.git --branch ${{ env.BRANCH_NAME }} - cd cake_wallet/scripts/android/ - ./install_ndk.sh - source ./app_env.sh cakewallet - chmod +x pubspec_gen.sh - ./app_config.sh - - - name: Cache Externals - id: cache-externals - uses: actions/cache@v3 - with: - path: | - /opt/android/cake_wallet/cw_haven/android/.cxx - /opt/android/cake_wallet/scripts/monero_c/release - key: ${{ hashFiles('**/prepare_moneroc.sh' ,'**/build_monero_all.sh' ,'**/cache_dependencies.yml') }} - - - if: ${{ steps.cache-externals.outputs.cache-hit != 'true' }} - name: Generate Externals - run: | - cd /opt/android/cake_wallet/scripts/android/ - source ./app_env.sh cakewallet - ./build_monero_all.sh - - - name: Install Flutter dependencies - run: | - cd /opt/android/cake_wallet - flutter pub get - - - - name: Install go and gomobile - run: | - # install go > 1.23: - wget https://go.dev/dl/go1.23.1.linux-amd64.tar.gz - sudo rm -rf /usr/local/go && sudo tar -C /usr/local -xzf go1.23.1.linux-amd64.tar.gz - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - go install golang.org/x/mobile/cmd/gomobile@latest - gomobile init - - - name: Build mwebd - run: | - # paths are reset after each step, so we need to set them again: - export PATH=$PATH:/usr/local/go/bin - export PATH=$PATH:~/go/bin - cd /opt/android/cake_wallet/scripts/android/ - ./build_mwebd.sh --dont-install - - - name: Generate KeyStore - run: | - cd /opt/android/cake_wallet/android/app - keytool -genkey -v -keystore key.jks -keyalg RSA -keysize 2048 -validity 10000 -alias testKey -noprompt -dname "CN=CakeWallet, OU=CakeWallet, O=CakeWallet, L=Florida, S=America, C=USA" -storepass $STORE_PASS -keypass $KEY_PASS - - - name: Generate key properties - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_android_key_properties.dart keyAlias=testKey storeFile=key.jks storePassword=$STORE_PASS keyPassword=$KEY_PASS - - - name: Generate localization - run: | - cd /opt/android/cake_wallet - flutter packages pub run tool/generate_localization.dart - - - name: Build generated code - run: | - cd /opt/android/cake_wallet - ./model_generator.sh - - - name: Add secrets - run: | - cd /opt/android/cake_wallet - touch lib/.secrets.g.dart - touch cw_evm/lib/.secrets.g.dart - touch cw_solana/lib/.secrets.g.dart - touch cw_core/lib/.secrets.g.dart - touch cw_nano/lib/.secrets.g.dart - touch cw_tron/lib/.secrets.g.dart - echo "const salt = '${{ secrets.SALT }}';" > lib/.secrets.g.dart - echo "const keychainSalt = '${{ secrets.KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const key = '${{ secrets.KEY }}';" >> lib/.secrets.g.dart - echo "const walletSalt = '${{ secrets.WALLET_SALT }}';" >> lib/.secrets.g.dart - echo "const shortKey = '${{ secrets.SHORT_KEY }}';" >> lib/.secrets.g.dart - echo "const backupSalt = '${{ secrets.BACKUP_SALT }}';" >> lib/.secrets.g.dart - echo "const backupKeychainSalt = '${{ secrets.BACKUP_KEY_CHAIN_SALT }}';" >> lib/.secrets.g.dart - echo "const changeNowApiKey = '${{ secrets.CHANGE_NOW_API_KEY }}';" >> lib/.secrets.g.dart - echo "const changeNowApiKeyDesktop = '${{ secrets.CHANGE_NOW_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const wyreSecretKey = '${{ secrets.WYRE_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreApiKey = '${{ secrets.WYRE_API_KEY }}';" >> lib/.secrets.g.dart - echo "const wyreAccountId = '${{ secrets.WYRE_ACCOUNT_ID }}';" >> lib/.secrets.g.dart - echo "const moonPayApiKey = '${{ secrets.MOON_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const moonPaySecretKey = '${{ secrets.MOON_PAY_SECRET_KEY }}';" >> lib/.secrets.g.dart - echo "const sideShiftAffiliateId = '${{ secrets.SIDE_SHIFT_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKey = '${{ secrets.SIMPLE_SWAP_API_KEY }}';" >> lib/.secrets.g.dart - echo "const simpleSwapApiKeyDesktop = '${{ secrets.SIMPLE_SWAP_API_KEY_DESKTOP }}';" >> lib/.secrets.g.dart - echo "const onramperApiKey = '${{ secrets.ONRAMPER_API_KEY }}';" >> lib/.secrets.g.dart - echo "const anypayToken = '${{ secrets.ANY_PAY_TOKEN }}';" >> lib/.secrets.g.dart - echo "const ioniaClientId = '${{ secrets.IONIA_CLIENT_ID }}';" >> lib/.secrets.g.dart - echo "const twitterBearerToken = '${{ secrets.TWITTER_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const trocadorApiKey = '${{ secrets.TROCADOR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const trocadorExchangeMarkup = '${{ secrets.TROCADOR_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const anonPayReferralCode = '${{ secrets.ANON_PAY_REFERRAL_CODE }}';" >> lib/.secrets.g.dart - echo "const fiatApiKey = '${{ secrets.FIAT_API_KEY }}';" >> lib/.secrets.g.dart - echo "const payfuraApiKey = '${{ secrets.PAYFURA_API_KEY }}';" >> lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> lib/.secrets.g.dart - echo "const etherScanApiKey = '${{ secrets.ETHER_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const chatwootWebsiteToken = '${{ secrets.CHATWOOT_WEBSITE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const exolixApiKey = '${{ secrets.EXOLIX_API_KEY }}';" >> lib/.secrets.g.dart - echo "const robinhoodApplicationId = '${{ secrets.ROBINHOOD_APPLICATION_ID }}';" >> lib/.secrets.g.dart - echo "const exchangeHelperApiKey = '${{ secrets.ROBINHOOD_CID_CLIENT_SECRET }}';" >> lib/.secrets.g.dart - echo "const walletConnectProjectId = '${{ secrets.WALLET_CONNECT_PROJECT_ID }}';" >> lib/.secrets.g.dart - echo "const moralisApiKey = '${{ secrets.MORALIS_API_KEY }}';" >> lib/.secrets.g.dart - echo "const polygonScanApiKey = '${{ secrets.POLYGON_SCAN_API_KEY }}';" >> cw_evm/lib/.secrets.g.dart - echo "const ankrApiKey = '${{ secrets.ANKR_API_KEY }}';" >> cw_solana/lib/.secrets.g.dart - echo "const testCakePayApiKey = '${{ secrets.TEST_CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const cakePayApiKey = '${{ secrets.CAKE_PAY_API_KEY }}';" >> lib/.secrets.g.dart - echo "const authorization = '${{ secrets.CAKE_PAY_AUTHORIZATION }}';" >> lib/.secrets.g.dart - echo "const CSRFToken = '${{ secrets.CSRF_TOKEN }}';" >> lib/.secrets.g.dart - echo "const quantexExchangeMarkup = '${{ secrets.QUANTEX_EXCHANGE_MARKUP }}';" >> lib/.secrets.g.dart - echo "const nano2ApiKey = '${{ secrets.NANO2_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const nanoNowNodesApiKey = '${{ secrets.NANO_NOW_NODES_API_KEY }}';" >> cw_nano/lib/.secrets.g.dart - echo "const tronGridApiKey = '${{ secrets.TRON_GRID_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const tronNowNodesApiKey = '${{ secrets.TRON_NOW_NODES_API_KEY }}';" >> cw_tron/lib/.secrets.g.dart - echo "const letsExchangeBearerToken = '${{ secrets.LETS_EXCHANGE_TOKEN }}';" >> lib/.secrets.g.dart - echo "const letsExchangeAffiliateId = '${{ secrets.LETS_EXCHANGE_AFFILIATE_ID }}';" >> lib/.secrets.g.dart - echo "const stealthExBearerToken = '${{ secrets.STEALTH_EX_BEARER_TOKEN }}';" >> lib/.secrets.g.dart - echo "const stealthExAdditionalFeePercent = '${{ secrets.STEALTH_EX_ADDITIONAL_FEE_PERCENT }}';" >> lib/.secrets.g.dart - - - name: Rename app - run: | - echo -e "id=com.cakewallet.test_${{ env.PR_NUMBER }}\nname=${{ env.BRANCH_NAME }}" > /opt/android/cake_wallet/android/app.properties - - - name: Build - run: | - cd /opt/android/cake_wallet - flutter build apk --release --split-per-abi - - - name: 🦾 Enable KVM - run: | - echo 'KERNEL=="kvm", GROUP="kvm", MODE="0666", OPTIONS+="static_node=kvm"' | sudo tee /etc/udev/rules.d/99-kvm4all.rules - sudo udevadm control --reload-rules - sudo udevadm trigger --name-match=kvm - - - name: 🦾 Cache gradle - uses: gradle/actions/setup-gradle@v3 - - - name: 🦾 Cache AVD - uses: actions/cache@v4 - id: avd-cache - with: - path: | - ~/.android/avd/* - ~/.android/adb* - key: avd-${{ matrix.api-level }} - - - name: 🦾 Create AVD and generate snapshot for caching - if: steps.avd-cache.outputs.cache-hit != 'true' - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - # arch: ${{ matrix.arch }} - emulator-options: -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: false - script: echo "Generated AVD snapshot for caching." - - - name: 🚀 Integration tests on Android Emualator - uses: reactivecircus/android-emulator-runner@v2 - with: - api-level: ${{ matrix.api-level }} - force-avd-creation: false - # arch: ${{ matrix.arch }} - emulator-options: -no-snapshot-save -no-window -gpu swiftshader_indirect -noaudio -no-boot-anim -camera-back none - disable-animations: true - script: | - cd /opt/android/cake_wallet - flutter pub get - flutter drive --driver=test_driver/integration_test.dart --target=integration_test/test_suites/ - if [ $? -ne 0 ]; then - echo "Tests failed" - exit 1 - fi