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; +}