From ef420847e0105c52365c75fc057205edb457c163 Mon Sep 17 00:00:00 2001 From: Philipp Hoenisch Date: Tue, 21 May 2024 11:48:34 +1000 Subject: [PATCH] fix(app-test): workaround for fake `GoRouter` which cant navigate We can't navigate using the mocked router (I don't understand why). Which means, we can't navigate to the new channel configuration flow. Because of this, I've split up the tests into more fine granular tests. We test now each widget separately. This has the advantage that our tests are smaller and easier to understand. It seems like that if we want to test navigation, we should write integration tests instead. --- .../channel_configuration_screen.dart | 2 + .../trade/trade_bottom_sheet_tab.dart | 4 + mobile/lib/features/trade/trade_screen.dart | 7 +- mobile/lib/util/constants.dart | 27 +- mobile/test/trade_test.dart | 393 ++++++++++++++---- 5 files changed, 352 insertions(+), 81 deletions(-) diff --git a/mobile/lib/features/trade/channel_creation_flow/channel_configuration_screen.dart b/mobile/lib/features/trade/channel_creation_flow/channel_configuration_screen.dart index eaae1884d..762b471aa 100644 --- a/mobile/lib/features/trade/channel_creation_flow/channel_configuration_screen.dart +++ b/mobile/lib/features/trade/channel_creation_flow/channel_configuration_screen.dart @@ -383,6 +383,7 @@ class _ChannelConfiguration extends State { mainAxisAlignment: MainAxisAlignment.start, children: [ Checkbox( + key: tradeScreenBottomSheetChannelConfigurationFundWithWalletCheckBox, value: useInnerWallet, onChanged: fundWithWalletEnabled ? (bool? value) { @@ -442,6 +443,7 @@ class _ChannelConfiguration extends State { child: Padding( padding: const EdgeInsets.only(top: 1, left: 8, right: 8, bottom: 8), child: ConfirmationSlider( + key: tradeScreenBottomSheetChannelConfigurationConfirmSlider, text: "Swipe to confirm ${widget.tradeValues.direction.nameU}", textStyle: TextStyle(color: confirmationSliderColor), height: 40, diff --git a/mobile/lib/features/trade/trade_bottom_sheet_tab.dart b/mobile/lib/features/trade/trade_bottom_sheet_tab.dart index 68b3bc926..ee628eddb 100644 --- a/mobile/lib/features/trade/trade_bottom_sheet_tab.dart +++ b/mobile/lib/features/trade/trade_bottom_sheet_tab.dart @@ -22,6 +22,7 @@ import 'package:get_10101/features/trade/submit_order_change_notifier.dart'; import 'package:get_10101/features/trade/trade_bottom_sheet_confirmation.dart'; import 'package:get_10101/features/trade/trade_theme.dart'; import 'package:get_10101/features/trade/trade_value_change_notifier.dart'; +import 'package:get_10101/util/constants.dart'; import 'package:go_router/go_router.dart'; import 'package:provider/provider.dart'; @@ -231,6 +232,7 @@ class _TradeBottomSheetTabState extends State selector: (_, provider) => provider.fromDirection(direction).price ?? 0, builder: (context, price, child) { return UsdTextField( + key: tradeButtonSheetMarketPrice, value: Usd.fromDouble(price), label: "Market Price (USD)", ); @@ -240,6 +242,7 @@ class _TradeBottomSheetTabState extends State children: [ Flexible( child: AmountInputField( + key: tradeButtonSheetQuantityInput, controller: quantityController, suffixIcon: TextButton( onPressed: () { @@ -302,6 +305,7 @@ class _TradeBottomSheetTabState extends State provider.fromDirection(direction).margin ?? Amount.zero(), builder: (context, margin, child) { return AmountTextField( + key: tradeButtonSheetMarginField, value: margin, label: "Margin (sats)", ); diff --git a/mobile/lib/features/trade/trade_screen.dart b/mobile/lib/features/trade/trade_screen.dart index fc13a17d2..e6c132b39 100644 --- a/mobile/lib/features/trade/trade_screen.dart +++ b/mobile/lib/features/trade/trade_screen.dart @@ -61,6 +61,7 @@ class TradeScreen extends StatelessWidget { return provider.getAskPrice(); }, builder: (context, price, child) { return LatestPriceWidget( + innerKey: tradeScreenAskPrice, label: "Latest Ask: ", price: Usd.fromDouble(price ?? 0.0), ); @@ -69,6 +70,7 @@ class TradeScreen extends StatelessWidget { return provider.getBidPrice(); }, builder: (context, price, child) { return LatestPriceWidget( + innerKey: tradeScreenBidPrice, label: "Latest Bid: ", price: Usd.fromDouble(price ?? 0.0), ); @@ -261,12 +263,15 @@ class TradeScreen extends StatelessWidget { class LatestPriceWidget extends StatelessWidget { final Usd price; final String label; + final Key innerKey; - const LatestPriceWidget({super.key, required this.price, required this.label}); + const LatestPriceWidget( + {super.key, required this.price, required this.label, required this.innerKey}); @override Widget build(BuildContext context) { return RichText( + key: innerKey, text: TextSpan( text: label, style: DefaultTextStyle.of(context).style, diff --git a/mobile/lib/util/constants.dart b/mobile/lib/util/constants.dart index 4364751da..0e902403b 100644 --- a/mobile/lib/util/constants.dart +++ b/mobile/lib/util/constants.dart @@ -31,7 +31,8 @@ const _buy = "buy"; const _sell = "sell"; const _positions = "positions"; const _orders = "orders"; -const _configureChannel = "configure_channel"; +const _confirmationButton = "confirmation_button"; +const _confirmationSlider = "confirmation_slider"; const _openChannel = "open_channel"; const tradeScreenTabsOrders = Key(_trade + _tabs + _orders); @@ -46,9 +47,6 @@ const tradeScreenBottomSheetTabsSell = Key(_trade + _bottomSheet + _tabs + _sell const tradeScreenBottomSheetButtonBuy = Key(_trade + _bottomSheet + _button + _buy); const tradeScreenBottomSheetButtonSell = Key(_trade + _bottomSheet + _button + _sell); -const tradeScreenBottomSheetChannelConfigurationConfirmButton = - Key(_trade + _bottomSheet + _configureChannel); - const tradeScreenBottomSheetConfirmationConfigureChannelSlider = Key(_trade + _bottomSheet + _confirmSheet + _channelConfig + _slider + _openChannel); @@ -65,3 +63,24 @@ const tradeScreenBottomSheetConfirmationSliderButtonSell = const tabStable = Key(_tabs + _stable); const tabWallet = Key(_tabs + _wallet); const tabTrade = Key(_tabs + _trade); + +const _ask = "ask"; +const _bid = "bid"; +const _marketPrice = "marketPrice"; +const _quantityInput = "quantityInput"; +const _marginField = "marginField"; + +const tradeScreenAskPrice = Key(_trade + _tabs + _ask); +const tradeScreenBidPrice = Key(_trade + _tabs + _bid); + +const tradeButtonSheetMarketPrice = Key(_trade + _tabs + _bottomSheet + _marketPrice); +const tradeButtonSheetQuantityInput = Key(_trade + _tabs + _bottomSheet + _quantityInput); +const tradeButtonSheetMarginField = Key(_trade + _tabs + _bottomSheet + _marginField); + +const tradeScreenBottomSheetChannelConfigurationConfirmButton = + Key(_trade + _channelConfig + _confirmationButton); + +const tradeScreenBottomSheetChannelConfigurationConfirmSlider = + Key(_trade + _channelConfig + _confirmationSlider); +const tradeScreenBottomSheetChannelConfigurationFundWithWalletCheckBox = + Key(_trade + _channelConfig + _confirmationSlider); diff --git a/mobile/test/trade_test.dart b/mobile/test/trade_test.dart index 6c7ca85cd..d2f8b56be 100644 --- a/mobile/test/trade_test.dart +++ b/mobile/test/trade_test.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:flutter_test/flutter_test.dart'; import 'package:get_10101/bridge_generated/bridge_definitions.dart' as bridge; import 'package:get_10101/common/amount_denomination_change_notifier.dart'; +import 'package:get_10101/common/amount_text_field.dart'; @GenerateNiceMocks([MockSpec()]) import 'package:get_10101/common/application/channel_info_service.dart'; import 'package:get_10101/common/application/tentenone_config_change_notifier.dart'; @@ -16,7 +17,9 @@ import 'package:get_10101/features/trade/application/order_service.dart'; import 'package:get_10101/features/trade/application/position_service.dart'; @GenerateNiceMocks([MockSpec()]) import 'package:get_10101/features/trade/application/trade_values_service.dart'; +import 'package:get_10101/features/trade/channel_creation_flow/channel_configuration_screen.dart'; import 'package:get_10101/features/trade/domain/direction.dart'; +import 'package:get_10101/features/trade/domain/leverage.dart'; import 'package:get_10101/features/trade/order_change_notifier.dart'; import 'package:get_10101/features/trade/position_change_notifier.dart'; import 'package:get_10101/features/trade/submit_order_change_notifier.dart'; @@ -38,28 +41,38 @@ import 'package:slide_to_confirm/slide_to_confirm.dart'; import 'trade_test.mocks.dart'; -final GoRouter _router = GoRouter( - initialLocation: TradeScreen.route, - routes: [ - GoRoute( - path: TradeScreen.route, - builder: (BuildContext context, GoRouterState state) { - return const TradeScreen(); - }), - ], -); +GoRouter buildGoRouterMock(String initialLocation) { + return GoRouter( + initialLocation: initialLocation, + routes: [ + GoRoute( + path: TradeScreen.route, + builder: (BuildContext context, GoRouterState state) { + return const TradeScreen(); + }), + GoRoute( + path: ChannelConfigurationScreen.route, + builder: (BuildContext context, GoRouterState state) { + return const ChannelConfigurationScreen( + direction: Direction.long, + ); + }), + ], + ); +} class TestWrapperWithTradeTheme extends StatelessWidget { final Widget child; + final RouterConfig router; - const TestWrapperWithTradeTheme({super.key, required this.child}); + const TestWrapperWithTradeTheme({super.key, required this.child, required this.router}); @override Widget build(BuildContext context) { return MaterialApp.router( // TODO: We could consider using the Navigator instead of GoRouter to close the bottom sheet again // Need GoRouter otherwise closing the bottom sheet after confirmation fails - routerConfig: _router, + routerConfig: router, theme: ThemeData( primarySwatch: Colors.blue, extensions: const >[ @@ -81,15 +94,79 @@ void main() { MockDlcChannelService dlcChannelService = MockDlcChannelService(); MockOrderService orderService = MockOrderService(); - testWidgets('Given trade screen when completing first buy flow then market order is submitted', - (tester) async { - // TODO: we could make this more resilient in the underlying components... - // return dummies otherwise the fields won't be initialized correctly + testWidgets('Given rates, the trade screen show bid/ask price', (tester) async { + final tradeValuesChangeNotifier = TradeValuesChangeNotifier(tradeValueService); + SubmitOrderChangeNotifier submitOrderChangeNotifier = SubmitOrderChangeNotifier(orderService); + PositionChangeNotifier positionChangeNotifier = PositionChangeNotifier(positionService); + + const askPrice = 30001.0; + const bidPrice = 30000.0; + + positionChangeNotifier.askPrice = askPrice; + positionChangeNotifier.bidPrice = bidPrice; + tradeValuesChangeNotifier.updatePrice(askPrice, Direction.short); + tradeValuesChangeNotifier.updatePrice(bidPrice, Direction.long); + + // We start the trade screen + await tester.pumpWidget(MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => tradeValuesChangeNotifier), + ChangeNotifierProvider(create: (context) => submitOrderChangeNotifier), + ChangeNotifierProvider(create: (context) => OrderChangeNotifier(orderService)), + ChangeNotifierProvider(create: (context) => positionChangeNotifier), + ], + child: TestWrapperWithTradeTheme( + router: buildGoRouterMock(TradeScreen.route), + child: const TradeScreen(), + ))); + logger.i("Trade screen started"); + + // We check if all the widgets are here which we want to see + var tradeScreenAskPriceWidget = find.byKey(tradeScreenAskPrice); + expect(tradeScreenAskPriceWidget, findsOneWidget); + var assertedPrice = assertPrice(tester, tradeScreenAskPriceWidget, "\$ 30,001"); + logger.i("Ask price found: $assertedPrice"); + var tradeScreenBidPriceWidget = find.byKey(tradeScreenBidPrice); + expect(tradeScreenBidPriceWidget, findsOneWidget); + assertedPrice = assertPrice(tester, tradeScreenBidPriceWidget, "\$ 30,000"); + logger.i("Bid price found: $assertedPrice"); + + // Buy and sell buttons are also here + expect(find.byKey(tradeScreenButtonBuy), findsOneWidget); + logger.i("Buy button found"); + expect(find.byKey(tradeScreenButtonSell), findsOneWidget); + logger.i("Sell button found"); + + // The two tabs for positions and orders are also here + expect(find.byKey(tradeScreenTabsPositions), findsOneWidget); + logger.i("Positions tab button found"); + expect(find.byKey(tradeScreenTabsOrders), findsOneWidget); + logger.i("Orders tab button found"); + }); + + testWidgets('Given price and balance we see maximum quantity and margin set', (tester) async { + final tradeValuesChangeNotifier = TradeValuesChangeNotifier(tradeValueService); + SubmitOrderChangeNotifier submitOrderChangeNotifier = SubmitOrderChangeNotifier(orderService); + PositionChangeNotifier positionChangeNotifier = PositionChangeNotifier(positionService); + TenTenOneConfigChangeNotifier configChangeNotifier = + TenTenOneConfigChangeNotifier(channelConstraintsService); + DlcChannelChangeNotifier dlcChannelChangeNotifier = DlcChannelChangeNotifier(dlcChannelService); + OrderChangeNotifier orderChangeNotifier = OrderChangeNotifier(orderService); + + const askPrice = 30001.0; + const bidPrice = 30000.0; + + positionChangeNotifier.askPrice = askPrice; + positionChangeNotifier.bidPrice = bidPrice; + tradeValuesChangeNotifier.updatePrice(askPrice, Direction.short); + tradeValuesChangeNotifier.updatePrice(bidPrice, Direction.long); + + var mockedDefaultMargin = Amount(1000); when(tradeValueService.calculateMargin( price: anyNamed('price'), quantity: anyNamed('quantity'), leverage: anyNamed('leverage'))) - .thenReturn(Amount(1000)); + .thenReturn(mockedDefaultMargin); when(tradeValueService.calculateLiquidationPrice( price: anyNamed('price'), leverage: anyNamed('leverage'), @@ -102,15 +179,14 @@ void main() { when(tradeValueService.orderMatchingFee( quantity: anyNamed('quantity'), price: anyNamed('price'))) .thenReturn(Amount(42)); + var mockedMaxQuantity = Usd(2500); when(tradeValueService.calculateMaxQuantity( price: anyNamed('price'), leverage: anyNamed('leverage'), direction: anyNamed('direction'))) - .thenReturn(Usd(2500)); - - when(dlcChannelService.getEstimatedChannelFeeReserve()).thenReturn((Amount(500))); - - when(dlcChannelService.getEstimatedFundingTxFee()).thenReturn((Amount(300))); + .thenReturn(mockedMaxQuantity); + when(dlcChannelService.getEstimatedChannelFeeReserve()).thenReturn(Amount(123)); + when(dlcChannelService.getEstimatedFundingTxFee()).thenReturn(Amount(42)); when(channelConstraintsService.getTradeConstraints()).thenAnswer((_) => const bridge.TradeConstraints( @@ -123,71 +199,213 @@ void main() { maintenanceMarginRate: 0.1, orderMatchingFeeRate: 0.003)); - SubmitOrderChangeNotifier submitOrderChangeNotifier = SubmitOrderChangeNotifier(orderService); + // We start the trade screen + await tester.pumpWidget(MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => tradeValuesChangeNotifier), + ChangeNotifierProvider(create: (context) => configChangeNotifier), + ChangeNotifierProvider(create: (context) => dlcChannelChangeNotifier), + ChangeNotifierProvider(create: (context) => orderChangeNotifier), + ChangeNotifierProvider(create: (context) => submitOrderChangeNotifier), + ChangeNotifierProvider(create: (context) => positionChangeNotifier), + ChangeNotifierProvider(create: (context) => AmountDenominationChangeNotifier()), + ], + child: TestWrapperWithTradeTheme( + router: buildGoRouterMock(TradeScreen.route), + child: const TradeScreen(), + ))); - WalletChangeNotifier walletChangeNotifier = WalletChangeNotifier(walletService); + logger.i("Trade screen started"); - PositionChangeNotifier positionChangeNotifier = PositionChangeNotifier(positionService); + // Just check for the buy button to open the bottom sheet + expect(find.byKey(tradeScreenButtonBuy), findsOneWidget); + logger.i("Buy button found"); - TenTenOneConfigChangeNotifier configChangeNotifier = - TenTenOneConfigChangeNotifier(channelConstraintsService); + // Open bottom sheet + await tester.tap(find.byKey(tradeScreenButtonBuy)); + await tester.pumpAndSettle(); + logger.i("Trade bottom sheet opened"); + + // Assert market price + { + var marketPriceWidget = find.byKey(tradeButtonSheetMarketPrice); + expect(marketPriceWidget, findsOneWidget); + logger.i("Market price field found"); + + // Find the Text widget within the marketPriceWidget + final usdWidgetTextFields = find.descendant( + of: marketPriceWidget, + matching: find.byType(Text), + ); + + // Verify the Text widget is found + expect(usdWidgetTextFields, findsWidgets); + + // Check if the widget contains our market price + bool containsDesiredString = false; + usdWidgetTextFields.evaluate().forEach((element) { + final textWidget = element.widget as Text; + if (textWidget.data == "30,001") { + containsDesiredString = true; + } + }); + expect(containsDesiredString, isTrue); + logger.i("Market price found"); + } + + // Find quantity input field and assert this field is set + { + var quantityInputFieldWidget = find.byKey(tradeButtonSheetQuantityInput); + expect(quantityInputFieldWidget, findsOneWidget); + logger.i("Quantity input field found"); + // Find the input field widget + final quantityInputField = find.descendant( + of: quantityInputFieldWidget, + matching: find.byType(TextFormField), + ); + expect(quantityInputField, findsOneWidget); + + // Verify the default text in input field + final textFormField = tester.widget(quantityInputField); + expect(textFormField.controller?.text, mockedMaxQuantity.formatted()); + logger.i("Initial quantity field was set to: ${textFormField.controller?.text}"); + } + + // Find margin field and verify it has been set correctly + { + verifyMarginFieldValueSet(tester, mockedDefaultMargin); + } + + // Update the input field and verify that margin has been recomputed + { + var quantityInputFieldWidget = find.byKey(tradeButtonSheetQuantityInput); + expect(quantityInputFieldWidget, findsOneWidget); + logger.i("Quantity input field widget found"); + // Find the input field widget + final quantityInputField = find.descendant( + of: quantityInputFieldWidget, + matching: find.byType(TextFormField), + ); + expect(quantityInputField, findsOneWidget); + logger.i("Quantity input field found"); + + // Verify the default text in input field + final textFormField = tester.widget(quantityInputField); + // Enter text into the TextFormField + await tester.enterText(quantityInputField, '100'); + var inputQuantity = Usd(100); + expect(textFormField.controller?.text, inputQuantity.formatted()); + logger.i("Updated quantity field was set to: ${textFormField.controller?.text}"); + + verify(tradeValueService.calculateMargin( + price: 30001.0, quantity: inputQuantity, leverage: Leverage(2))) + .called(greaterThan(1)); + + logger.i("Margin has been recalculated"); + } + + // we verify again if we can find the buy button but do not click it + // our test setup does not support navigating unfortunately + expect(find.byKey(tradeScreenBottomSheetButtonBuy), findsOneWidget); + logger.i("Found buy button"); + }); - DlcChannelChangeNotifier dlcChannelChangeNotifier = DlcChannelChangeNotifier(dlcChannelService); + testWidgets('when funding with internal wallet, then market buy order is created', + (tester) async { + // This is to ensure we don't get random overflows. The dimensions are from an iPhone 15 + await tester.binding.setSurfaceSize(const Size(2556, 1179)); final tradeValuesChangeNotifier = TradeValuesChangeNotifier(tradeValueService); + SubmitOrderChangeNotifier submitOrderChangeNotifier = SubmitOrderChangeNotifier(orderService); + PositionChangeNotifier positionChangeNotifier = PositionChangeNotifier(positionService); + TenTenOneConfigChangeNotifier configChangeNotifier = + TenTenOneConfigChangeNotifier(channelConstraintsService); + DlcChannelChangeNotifier dlcChannelChangeNotifier = DlcChannelChangeNotifier(dlcChannelService); + OrderChangeNotifier orderChangeNotifier = OrderChangeNotifier(orderService); - const askPrice = 30000.0; + const askPrice = 30001.0; const bidPrice = 30000.0; - // We have to have current price, otherwise we can't take order + var mockedDefaultMargin = Amount(1000); + when(tradeValueService.calculateMargin( + price: anyNamed('price'), + quantity: anyNamed('quantity'), + leverage: anyNamed('leverage'))) + .thenReturn(mockedDefaultMargin); + when(tradeValueService.calculateLiquidationPrice( + price: anyNamed('price'), + leverage: anyNamed('leverage'), + direction: anyNamed('direction'))) + .thenReturn(10000); + when(tradeValueService.calculateQuantity( + price: anyNamed('price'), leverage: anyNamed('leverage'), margin: anyNamed('margin'))) + .thenReturn(Usd(1)); + when(tradeValueService.getExpiryTimestamp()).thenReturn(DateTime.now()); + when(tradeValueService.orderMatchingFee( + quantity: anyNamed('quantity'), price: anyNamed('price'))) + .thenReturn(Amount(42)); + var mockedMaxQuantity = Usd(2500); + when(tradeValueService.calculateMaxQuantity( + price: anyNamed('price'), + leverage: anyNamed('leverage'), + direction: anyNamed('direction'))) + .thenReturn(mockedMaxQuantity); + when(dlcChannelService.getEstimatedChannelFeeReserve()).thenReturn(Amount(123)); + when(dlcChannelService.getEstimatedFundingTxFee()).thenReturn(Amount(42)); + positionChangeNotifier.askPrice = askPrice; positionChangeNotifier.bidPrice = bidPrice; + tradeValuesChangeNotifier.maxQuantityLock = false; tradeValuesChangeNotifier.updatePrice(askPrice, Direction.short); tradeValuesChangeNotifier.updatePrice(bidPrice, Direction.long); - await tester.pumpWidget(MultiProvider(providers: [ - ChangeNotifierProvider(create: (context) => tradeValuesChangeNotifier), - ChangeNotifierProvider(create: (context) => submitOrderChangeNotifier), - ChangeNotifierProvider(create: (context) => OrderChangeNotifier(orderService)), - ChangeNotifierProvider(create: (context) => positionChangeNotifier), - ChangeNotifierProvider(create: (context) => AmountDenominationChangeNotifier()), - ChangeNotifierProvider(create: (context) => walletChangeNotifier), - ChangeNotifierProvider(create: (context) => configChangeNotifier), - ChangeNotifierProvider(create: (context) => dlcChannelChangeNotifier), - ], child: const TestWrapperWithTradeTheme(child: TradeScreen()))); - - // We have to pretend that we have a balance, because otherwise the trade bottom sheet validation will not allow us to go to the confirmation screen - walletChangeNotifier.update(WalletInfo( - balances: WalletBalances(onChain: Amount(251000), offChain: Amount(100000)), history: [])); - - await tester.pumpAndSettle(); - - expect(find.byKey(tradeScreenButtonBuy), findsOneWidget); - - // Open bottom sheet - await tester.tap(find.byKey(tradeScreenButtonBuy)); - await tester.pumpAndSettle(); + when(channelConstraintsService.getTradeConstraints()).thenAnswer((_) => + const bridge.TradeConstraints( + maxLocalBalanceSats: 10000000, + maxCounterpartyBalanceSats: 20000000, + coordinatorLeverage: 2, + minQuantity: 1, + isChannelBalance: true, + minMargin: 1, + maintenanceMarginRate: 0.1, + orderMatchingFeeRate: 0.003)); - expect(find.byKey(tradeScreenBottomSheetButtonBuy), findsOneWidget); + // We start the trade screen + await tester.pumpWidget(MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => tradeValuesChangeNotifier), + ChangeNotifierProvider(create: (context) => configChangeNotifier), + ChangeNotifierProvider(create: (context) => dlcChannelChangeNotifier), + ChangeNotifierProvider(create: (context) => orderChangeNotifier), + ChangeNotifierProvider(create: (context) => submitOrderChangeNotifier), + ChangeNotifierProvider(create: (context) => positionChangeNotifier), + ChangeNotifierProvider(create: (context) => AmountDenominationChangeNotifier()), + ], + child: TestWrapperWithTradeTheme( + router: buildGoRouterMock(ChannelConfigurationScreen.route), + child: const ChannelConfigurationScreen(direction: Direction.long), + ))); - // click buy button in bottom sheet - await tester.tap(find.byKey(tradeScreenBottomSheetButtonBuy)); - await tester.pumpAndSettle(); + logger.i("Channel configuration screen started"); expect(find.byKey(tradeScreenBottomSheetChannelConfigurationConfirmButton), findsOneWidget); + logger.i("Confirmation button is present"); + var checkboxFinder = + find.byKey(tradeScreenBottomSheetChannelConfigurationFundWithWalletCheckBox); + expect(checkboxFinder, findsOneWidget); + logger.i("Checkbox is present"); + + // Tap the checkbox to check it + await tester.tap(checkboxFinder); await tester.pumpAndSettle(); + logger.i("Checked the checkbox"); - await tester.ensureVisible(find.byKey(tradeScreenBottomSheetChannelConfigurationConfirmButton)); - - // click confirm button to go to confirmation screen - await tester.tap(find.byKey(tradeScreenBottomSheetChannelConfigurationConfirmButton)); - await tester.pumpAndSettle(); + // Verify the checkbox is checked + expect(tester.widget(checkboxFinder).value, true); + logger.i("Verified that it is checked"); - // TODO: Use `find.byKey(tradeScreenBottomSheetConfirmationConfigureChannelSlider)`. - // For some reason the specific widget cannot be found. - expect(find.byType(ConfirmationSlider), findsOneWidget); - - // Drag to confirm + expect(find.byKey(tradeScreenBottomSheetChannelConfigurationConfirmSlider), findsOneWidget); + logger.i("Confirmation slider is now present"); // TODO: This is not optimal because if we re-style the component this test will likely break. final Offset sliderLocation = tester.getBottomLeft(find.byType(ConfirmationSlider)); @@ -265,16 +483,21 @@ void main() { tradeValuesChangeNotifier.updatePrice(askPrice, Direction.short); tradeValuesChangeNotifier.updatePrice(bidPrice, Direction.long); - await tester.pumpWidget(MultiProvider(providers: [ - ChangeNotifierProvider(create: (context) => tradeValuesChangeNotifier), - ChangeNotifierProvider(create: (context) => submitOrderChangeNotifier), - ChangeNotifierProvider(create: (context) => OrderChangeNotifier(orderService)), - ChangeNotifierProvider(create: (context) => positionChangeNotifier), - ChangeNotifierProvider(create: (context) => AmountDenominationChangeNotifier()), - ChangeNotifierProvider(create: (context) => walletChangeNotifier), - ChangeNotifierProvider(create: (context) => configChangeNotifier), - ChangeNotifierProvider(create: (context) => dlcChannelChangeNotifier), - ], child: const TestWrapperWithTradeTheme(child: TradeScreen()))); + await tester.pumpWidget(MultiProvider( + providers: [ + ChangeNotifierProvider(create: (context) => tradeValuesChangeNotifier), + ChangeNotifierProvider(create: (context) => submitOrderChangeNotifier), + ChangeNotifierProvider(create: (context) => OrderChangeNotifier(orderService)), + ChangeNotifierProvider(create: (context) => positionChangeNotifier), + ChangeNotifierProvider(create: (context) => AmountDenominationChangeNotifier()), + ChangeNotifierProvider(create: (context) => walletChangeNotifier), + ChangeNotifierProvider(create: (context) => configChangeNotifier), + ChangeNotifierProvider(create: (context) => dlcChannelChangeNotifier), + ], + child: TestWrapperWithTradeTheme( + router: buildGoRouterMock(TradeScreen.route), + child: const TradeScreen(), + ))); // We have to pretend that we have a balance, because otherwise the trade bottom sheet validation will not allow us to go to the confirmation screen walletChangeNotifier.update(WalletInfo( @@ -303,3 +526,21 @@ void main() { verify(orderService.submitMarketOrder(any, any, any, any, any)).called(1); }); } + +void verifyMarginFieldValueSet(WidgetTester tester, Amount mockedDefaultMargin) { + var marginFieldWidget = find.byKey(tradeButtonSheetMarginField); + expect(marginFieldWidget, findsOneWidget); + logger.i("Margin field found"); + final amountField = tester.widget(marginFieldWidget); + expect(amountField.value, mockedDefaultMargin); + logger.i("Margin field set correctly to $mockedDefaultMargin"); +} + +String assertPrice(WidgetTester tester, Finder byKey, String priceString) { + final textWidget = tester.widget(byKey); + var text = textWidget.text as TextSpan; + var children = text.children!.first; + var plainText = children.toPlainText(); + expect(plainText, priceString); + return plainText; +}