From 46cfb6fc4acf28ab15e85c74b54099be467ec4b4 Mon Sep 17 00:00:00 2001 From: joshua Date: Fri, 5 Jun 2026 20:56:18 +0200 Subject: [PATCH] feat(buy/sell): show hint when price source (Aktionariat) is unavailable When the DFX brokerbot price endpoints fail (e.g. today's outage: HTTP 500 "Cannot destructure property 'priceInCHF'"), the buy/sell converters previously swallowed the error and left a frozen input with no explanation. Now the converter state carries a `priceUnavailable` flag, set on any price-fetch error and cleared on the next success, and the buy/sell pages render a small non-blocking PaymentActionRequired hint under the converter. - buy/sell converter state: add `priceUnavailable` flag - buy/sell converter cubits: set flag on error, clear on success - buy_page/sell_page: render reused PaymentActionRequired notice - i18n: add priceSourceUnavailableTitle/Description (DE + EN) - tests: flag set-on-error / clear-on-success for both cubits Co-Authored-By: Claude Opus 4.8 --- assets/languages/strings_de.arb | 20 +++++++------- assets/languages/strings_en.arb | 20 +++++++------- lib/screens/buy/buy_page.dart | 9 +++++++ .../buy_converter/buy_converter_cubit.dart | 9 ++++--- .../buy_converter/buy_converter_state.dart | 6 ++++- .../sell_converter/sell_converter_cubit.dart | 9 ++++--- .../sell_converter/sell_converter_state.dart | 6 ++++- lib/screens/sell/sell_page.dart | 7 +++++ .../buy/cubits/buy_converter_cubit_test.dart | 26 +++++++++++++++++++ .../cubits/sell_converter_cubit_test.dart | 26 +++++++++++++++++++ 10 files changed, 112 insertions(+), 26 deletions(-) diff --git a/assets/languages/strings_de.arb b/assets/languages/strings_de.arb index e3f21595..8855366b 100644 --- a/assets/languages/strings_de.arb +++ b/assets/languages/strings_de.arb @@ -33,8 +33,8 @@ "buyPaymentConfirmFailedAktionariat": "Es gibt ein technisches Problem. Bitte überprüfen Sie Ihr E-Mail-Postfach, möglicherweise fehlt noch eine Bestätigung Ihrer Blockchain-Adresse. Andernfalls versuchen Sie es später erneut. Falls der Fehler weiterhin besteht, kontaktieren Sie unseren Support.", "buyPaymentInformation": "Zahlungsinformationen", "buyPaymentInformationDescription": "Bitte überweisen Sie den Kaufbetrag mit diesen Angaben über Ihre Bankanwendung. Der Verwendungszweck ist wichtig!", - "buyRealUnit": "RealUnit kaufen", "buyRealu": "RealUnit Token kaufen", + "buyRealUnit": "RealUnit kaufen", "cancel": "Abbrechen", "changeAddress": "Adresse ändern", "changeInReview": "Änderung in Prüfung", @@ -53,11 +53,11 @@ "connectBitboxContent": "Bitte verbinden Sie Ihre BitBox mit Ihrem Smartphone.", "connectBitboxContentIos": "Bitte verbinden Sie Ihre BitBox mit Ihrem Smartphone und aktivieren Sie zusätzlich Bluetooth.", "connectBitboxFailed": "Es ist ein Fehler aufgetreten. Bitte versuchen Sie es erneut.", - "connectBitboxSignInHint": "Nach der Code-Überprüfung wird die BitBox um eine zusätzliche Bestätigung zur Anmeldung gebeten.", "connectBitboxSignatureCapturing": "Bitte bestätigen Sie die Anmeldeanfrage auf Ihrem BitBox-Gerät. Diese Signatur wird einmalig erfasst, damit künftige Käufe Ihre BitBox nicht erneut benötigen.", "connectBitboxSignatureCapturingTitle": "Anmeldung bestätigen", "connectBitboxSignatureFailed": "Ihre Anmeldesignatur konnte nicht erfasst werden. Sie können es erneut versuchen oder trotzdem fortfahren – Ihre BitBox wird dann möglicherweise für Ihren ersten Kauf erneut benötigt.", "connectBitboxSignatureFailedTitle": "Anmeldung nicht abgeschlossen", + "connectBitboxSignInHint": "Nach der Code-Überprüfung wird die BitBox um eine zusätzliche Bestätigung zur Anmeldung gebeten.", "connectBitboxTitle": "BitBox verbinden", "connected": "Verbunden", "connectedBitboxContent": "Bitte bestätigen Sie und folgen nun den letzten Anweisungen auf Ihrer BitBox.", @@ -194,11 +194,13 @@ "portfolio": "Bestand", "portfolioDevelopment": "Bestandsentwicklung", "postcodeAbr": "PLZ", + "priceSourceUnavailableDescription": "Die Verbindung zum Kursdienst (Aktionariat) ist vorübergehend gestört. Der aktuelle Kurs kann nicht geladen werden — bitte versuchen Sie es später erneut.", + "priceSourceUnavailableTitle": "Kurs derzeit nicht verfügbar", "proofDocument": "Nachweis-Dokument", "purposeOfPayment": "Verwendungszweck", "qrCode": "QR-Code", - "realunitStockToken": "RealUnit Aktientoken", "realunitStockprice": "RealUnit Aktienkurs", + "realunitStockToken": "RealUnit Aktientoken", "realunitWallet": "RealUnit Wallet", "realunitWalletLogout": "Aus REALU Wallet abmelden", "realunitWalletLogoutCheck": "Ich habe meine Wiederherstellungsphrase gesichert.", @@ -246,18 +248,18 @@ "sellBitboxCheckingEth": "Wallet-Guthaben wird geprüft", "sellBitboxDepositDescription": "Bestätigen Sie auf der BitBox, um ZCHF an die DFX-Einzahlungsadresse zu überweisen.", "sellBitboxDepositFrom": "Sie senden", + "sellBitboxDepositing": "ZCHF wird gesendet. Bestätigen Sie auf der Bitbox", "sellBitboxDepositRetryDescription": "Der Tausch wurde abgeschlossen, aber die ZCHF-Einzahlung konnte nicht gesendet werden. Ihre Mittel sind sicher. Tippen Sie auf Wiederholen.", "sellBitboxDepositRetryTitle": "Einzahlung fehlgeschlagen", "sellBitboxDepositTitle": "ZCHF an DFX senden", "sellBitboxDepositTo": "DFX-Einzahlung", - "sellBitboxDepositing": "ZCHF wird gesendet. Bestätigen Sie auf der Bitbox", "sellBitboxEthReady": "Wallet bereit", "sellBitboxEthReadyDescription": "Ihr Wallet hat genug ETH, um mit dem Verkauf fortzufahren.", "sellBitboxSwapDescription": "Bestätigen Sie auf Ihrem BitBox, um REALU über den BrokerBot in ZCHF zu tauschen.", "sellBitboxSwapFrom": "Sie senden", + "sellBitboxSwapping": "Tausch on-chain. Bestätigen Sie auf der Bitbox.", "sellBitboxSwapTitle": "REALU → ZCHF tauschen", "sellBitboxSwapTo": "Sie erhalten", - "sellBitboxSwapping": "Tausch on-chain. Bestätigen Sie auf der Bitbox.", "sellBitboxWaitingForEth": "Gasgebühren werden angefordert", "sellBitboxWaitingForEthDescription": "Ein kleiner ETH-Betrag wird an Ihr Wallet gesendet, um die Transaktionsgebühren zu decken. Dies kann einige Minuten dauern.", "sellMinAmount": "Mindestbetrag: ${amount} ${currency}", @@ -282,10 +284,10 @@ "settingsWalletBackupSubtitle1": "Bitte notieren Sie Ihre 12 Wiederherstellungs-Wörter in der exakten Reihenfolge auf einem Blatt Papier und bewahren Sie sie absolut sicher auf.", "settingsWalletBackupSubtitle2": "Dies ist die einzige Möglichkeit, Ihre Wallet wiederherzustellen.", "showSeed": "Seed anzeigen", - "signMessage": "Signierte Nachricht", - "signMessageGet": "Signierte Nachricht abrufen", "signature": "Signatur", "signingCancelled": "Signatur abgebrochen — bitte BitBox erneut bestätigen", + "signMessage": "Signierte Nachricht", + "signMessageGet": "Signierte Nachricht abrufen", "skip": "Überspringen", "softwareTermsText": "Mit der Nutzung dieser App akzeptieren Sie die Nutzungsbedingungen dieser Software.", "softwareTermsTextHighlighted": "Nutzungsbedingungen", @@ -329,9 +331,9 @@ "transactionBuy": "Kauf", "transactionHistory": "Transaktionshistorie", "transactionPending": "In Bearbeitung", + "transactions": "Transaktionen", "transactionSell": "Verkauf", "transactionWaitingForPayment": "Warte auf Zahlung", - "transactions": "Transaktionen", "twoFa": "2-Faktor Authentifizierung", "twoFaCodeRequired": "Code ist erforderlich", "twoFaCodeTooShort": "Der Code sollte 6 Ziffern lang sein", @@ -356,4 +358,4 @@ "youPay": "Sie bezahlen", "youReceive": "Sie erhalten", "youSell": "Sie verkaufen" -} +} \ No newline at end of file diff --git a/assets/languages/strings_en.arb b/assets/languages/strings_en.arb index 56e4db7f..1d0b7e68 100644 --- a/assets/languages/strings_en.arb +++ b/assets/languages/strings_en.arb @@ -33,8 +33,8 @@ "buyPaymentConfirmFailedAktionariat": "There is a technical problem. Please check your email inbox — you may still need to confirm your blockchain address. Otherwise, please try again later. If the error persists, contact our support team.", "buyPaymentInformation": "Payment information", "buyPaymentInformationDescription": "Please transfer the purchase amount using your banking app with these details. The purpose of payment is important!", - "buyRealUnit": "Buy RealUnit", "buyRealu": "Buy RealUnit Token", + "buyRealUnit": "Buy RealUnit", "cancel": "Cancel", "changeAddress": "Change address", "changeInReview": "Change in review", @@ -53,11 +53,11 @@ "connectBitboxContent": "Please connect your BitBox with your Smartphone.", "connectBitboxContentIos": "Please connect your BitBox with your Smartphone and activate Bluetooth.", "connectBitboxFailed": "Something went wrong. Please try to connect again.", - "connectBitboxSignInHint": "After verifying the code, the BitBox will ask for one additional confirmation to sign you in.", "connectBitboxSignatureCapturing": "Please confirm the sign-in request on your BitBox device. This signature is captured once so future purchases won't need your BitBox again.", "connectBitboxSignatureCapturingTitle": "Confirm sign-in", "connectBitboxSignatureFailed": "We couldn't capture your sign-in signature. You can retry, or continue anyway – your BitBox may then be needed again for your first purchase.", "connectBitboxSignatureFailedTitle": "Sign-in not completed", + "connectBitboxSignInHint": "After verifying the code, the BitBox will ask for one additional confirmation to sign you in.", "connectBitboxTitle": "Connect BitBox", "connected": "Connected", "connectedBitboxContent": "Please confirm and follow the last steps on your BitBox.", @@ -194,11 +194,13 @@ "portfolio": "Portfolio", "portfolioDevelopment": "Portfolio development", "postcodeAbr": "Post code", + "priceSourceUnavailableDescription": "The connection to the price service (Aktionariat) is temporarily unavailable. The current price cannot be loaded — please try again later.", + "priceSourceUnavailableTitle": "Price currently unavailable", "proofDocument": "Proof document", "purposeOfPayment": "Purpose of payment", "qrCode": "QR code", - "realunitStockToken": "RealUnit Stock Token", "realunitStockprice": "RealUnit Stockprice", + "realunitStockToken": "RealUnit Stock Token", "realunitWallet": "RealUnit Wallet", "realunitWalletLogout": "Log out of REALU Wallet", "realunitWalletLogoutCheck": "I have backed up my recovery phrase.", @@ -246,18 +248,18 @@ "sellBitboxCheckingEth": "Checking your wallet balance", "sellBitboxDepositDescription": "Confirm on your BitBox to transfer ZCHF to the DFX deposit address.", "sellBitboxDepositFrom": "You send", + "sellBitboxDepositing": "Sending ZCHF. Please confirm on the Bitbox.", "sellBitboxDepositRetryDescription": "The swap was completed but the ZCHF deposit could not be sent. Your funds are safe. Tap retry to try again.", "sellBitboxDepositRetryTitle": "Deposit failed", "sellBitboxDepositTitle": "Send ZCHF to DFX", "sellBitboxDepositTo": "DFX deposit", - "sellBitboxDepositing": "Sending ZCHF. Please confirm on the Bitbox.", "sellBitboxEthReady": "Wallet ready", "sellBitboxEthReadyDescription": "Your wallet has enough ETH to proceed with the sale.", "sellBitboxSwapDescription": "Confirm on your BitBox to swap REALU for ZCHF via the BrokerBot.", "sellBitboxSwapFrom": "You send", + "sellBitboxSwapping": "Swapping on-chain. Please confirm on the Bitbox.", "sellBitboxSwapTitle": "Swap REALU → ZCHF", "sellBitboxSwapTo": "You receive", - "sellBitboxSwapping": "Swapping on-chain. Please confirm on the Bitbox.", "sellBitboxWaitingForEth": "Requesting gas funds", "sellBitboxWaitingForEthDescription": "A small amount of ETH is being sent to your wallet to cover transaction fees. This may take a few minutes.", "sellMinAmount": "Minimum amount: ${amount} ${currency}", @@ -282,10 +284,10 @@ "settingsWalletBackupSubtitle1": "Please write down your 12 recovery words in the exact order on a piece of paper and keep them in a completely safe place.", "settingsWalletBackupSubtitle2": "This is the only way to recover your wallet.", "showSeed": "Show Seed", - "signMessage": "Sign Message", - "signMessageGet": "Get Sign Message", "signature": "Signature", "signingCancelled": "Signature cancelled — please confirm on the BitBox again", + "signMessage": "Sign Message", + "signMessageGet": "Get Sign Message", "skip": "Skip", "softwareTermsText": "By using this app, you accept the terms of use of this software.", "softwareTermsTextHighlighted": "terms of use", @@ -329,9 +331,9 @@ "transactionBuy": "Buy", "transactionHistory": "Transaction history", "transactionPending": "Processing", + "transactions": "Transactions", "transactionSell": "Sell", "transactionWaitingForPayment": "Waiting for payment", - "transactions": "Transactions", "twoFa": "Two-factor authentication", "twoFaCodeRequired": "Code is required", "twoFaCodeTooShort": "Code should be 6 digits", @@ -356,4 +358,4 @@ "youPay": "You pay", "youReceive": "You receive", "youSell": "You sell" -} +} \ No newline at end of file diff --git a/lib/screens/buy/buy_page.dart b/lib/screens/buy/buy_page.dart index a6d6b9e9..9911c2f4 100644 --- a/lib/screens/buy/buy_page.dart +++ b/lib/screens/buy/buy_page.dart @@ -5,6 +5,7 @@ import 'package:realunit_wallet/packages/service/dfx/dfx_brokerbot_service.dart' import 'package:realunit_wallet/packages/service/dfx/real_unit_buy_payment_info_service.dart'; import 'package:realunit_wallet/screens/buy/cubits/buy_converter/buy_converter_cubit.dart'; import 'package:realunit_wallet/screens/buy/cubits/buy_payment_info/buy_payment_info_cubit.dart'; +import 'package:realunit_wallet/screens/buy/widgets/payment_action_required.dart'; import 'package:realunit_wallet/screens/buy/widgets/payment_additional_action_needed_button.dart'; import 'package:realunit_wallet/screens/buy/widgets/payment_converter.dart'; import 'package:realunit_wallet/screens/buy/widgets/payment_information.dart'; @@ -81,6 +82,14 @@ class _BuyViewState extends State { amountController: _amountController, resultController: _resultController, ), + if (state.priceUnavailable) ...[ + const SizedBox(height: 16), + PaymentActionRequired( + title: S.of(context).priceSourceUnavailableTitle, + description: + S.of(context).priceSourceUnavailableDescription, + ), + ], const SizedBox(height: 32), PaymentInformation(amount: _amountController.text), const Spacer(), diff --git a/lib/screens/buy/cubits/buy_converter/buy_converter_cubit.dart b/lib/screens/buy/cubits/buy_converter/buy_converter_cubit.dart index 8fa7ac9a..a9b0fb81 100644 --- a/lib/screens/buy/cubits/buy_converter/buy_converter_cubit.dart +++ b/lib/screens/buy/cubits/buy_converter/buy_converter_cubit.dart @@ -43,12 +43,13 @@ class BuyConverterCubit extends Cubit { state.copyWith( sharesText: result.shares.toString(), loading: false, + priceUnavailable: false, ), ); } catch (e) { developer.log(e.toString()); if (isClosed || mySeq != _seq) return; - emit(state.copyWith(loading: false)); + emit(state.copyWith(loading: false, priceUnavailable: true)); } }); } @@ -70,12 +71,13 @@ class BuyConverterCubit extends Cubit { state.copyWith( fiatText: result.totalCost.toStringAsFixed(_fractionDigits(value)), loading: false, + priceUnavailable: false, ), ); } catch (e) { developer.log(e.toString()); if (isClosed || mySeq != _seq) return; - emit(state.copyWith(loading: false)); + emit(state.copyWith(loading: false, priceUnavailable: true)); } }); } @@ -94,12 +96,13 @@ class BuyConverterCubit extends Cubit { state.copyWith( sharesText: result.shares.toString(), loading: false, + priceUnavailable: false, ), ); } catch (e) { developer.log(e.toString()); if (isClosed || mySeq != _seq) return; - emit(state.copyWith(loading: false)); + emit(state.copyWith(loading: false, priceUnavailable: true)); } } diff --git a/lib/screens/buy/cubits/buy_converter/buy_converter_state.dart b/lib/screens/buy/cubits/buy_converter/buy_converter_state.dart index 7e2cfe40..87136ada 100644 --- a/lib/screens/buy/cubits/buy_converter/buy_converter_state.dart +++ b/lib/screens/buy/cubits/buy_converter/buy_converter_state.dart @@ -5,12 +5,14 @@ class BuyConverterState extends Equatable { final String sharesText; final Currency currency; final bool loading; + final bool priceUnavailable; const BuyConverterState({ this.fiatText = '', this.sharesText = '', this.currency = Currency.chf, this.loading = false, + this.priceUnavailable = false, }); BuyConverterState copyWith({ @@ -18,15 +20,17 @@ class BuyConverterState extends Equatable { String? sharesText, Currency? currency, bool? loading, + bool? priceUnavailable, }) { return BuyConverterState( fiatText: fiatText ?? this.fiatText, sharesText: sharesText ?? this.sharesText, currency: currency ?? this.currency, loading: loading ?? this.loading, + priceUnavailable: priceUnavailable ?? this.priceUnavailable, ); } @override - List get props => [fiatText, sharesText, currency, loading]; + List get props => [fiatText, sharesText, currency, loading, priceUnavailable]; } diff --git a/lib/screens/sell/cubits/sell_converter/sell_converter_cubit.dart b/lib/screens/sell/cubits/sell_converter/sell_converter_cubit.dart index 93660b47..ef0223c3 100644 --- a/lib/screens/sell/cubits/sell_converter/sell_converter_cubit.dart +++ b/lib/screens/sell/cubits/sell_converter/sell_converter_cubit.dart @@ -43,12 +43,13 @@ class SellConverterCubit extends Cubit { state.copyWith( sharesText: result.shares.toString(), loading: false, + priceUnavailable: false, ), ); } catch (e) { developer.log(e.toString()); if (isClosed || mySeq != _seq) return; - emit(state.copyWith(loading: false)); + emit(state.copyWith(loading: false, priceUnavailable: true)); } }); } @@ -70,12 +71,13 @@ class SellConverterCubit extends Cubit { state.copyWith( fiatText: result.estimatedAmount.toStringAsFixed(_fractionDigits(value)), loading: false, + priceUnavailable: false, ), ); } catch (e) { developer.log(e.toString()); if (isClosed || mySeq != _seq) return; - emit(state.copyWith(loading: false)); + emit(state.copyWith(loading: false, priceUnavailable: true)); } }); } @@ -93,12 +95,13 @@ class SellConverterCubit extends Cubit { state.copyWith( fiatText: result.totalCost.toStringAsFixed(_fractionDigits(state.sharesText)), loading: false, + priceUnavailable: false, ), ); } catch (e) { developer.log(e.toString()); if (isClosed || mySeq != _seq) return; - emit(state.copyWith(loading: false)); + emit(state.copyWith(loading: false, priceUnavailable: true)); } } diff --git a/lib/screens/sell/cubits/sell_converter/sell_converter_state.dart b/lib/screens/sell/cubits/sell_converter/sell_converter_state.dart index e029f1bf..5563f345 100644 --- a/lib/screens/sell/cubits/sell_converter/sell_converter_state.dart +++ b/lib/screens/sell/cubits/sell_converter/sell_converter_state.dart @@ -5,12 +5,14 @@ class SellConverterState extends Equatable { final String sharesText; final Currency currency; final bool loading; + final bool priceUnavailable; const SellConverterState({ this.fiatText = '', this.sharesText = '', this.currency = Currency.chf, this.loading = false, + this.priceUnavailable = false, }); SellConverterState copyWith({ @@ -18,15 +20,17 @@ class SellConverterState extends Equatable { String? sharesText, Currency? currency, bool? loading, + bool? priceUnavailable, }) { return SellConverterState( fiatText: fiatText ?? this.fiatText, sharesText: sharesText ?? this.sharesText, currency: currency ?? this.currency, loading: loading ?? this.loading, + priceUnavailable: priceUnavailable ?? this.priceUnavailable, ); } @override - List get props => [fiatText, sharesText, currency, loading]; + List get props => [fiatText, sharesText, currency, loading, priceUnavailable]; } diff --git a/lib/screens/sell/sell_page.dart b/lib/screens/sell/sell_page.dart index cf27a5de..25e6dd9f 100644 --- a/lib/screens/sell/sell_page.dart +++ b/lib/screens/sell/sell_page.dart @@ -8,6 +8,7 @@ import 'package:realunit_wallet/packages/service/dfx/real_unit_sell_payment_info import 'package:realunit_wallet/screens/sell/cubits/sell_balance/sell_balance_cubit.dart'; import 'package:realunit_wallet/screens/sell/cubits/sell_converter/sell_converter_cubit.dart'; import 'package:realunit_wallet/screens/sell/cubits/sell_payment_info/sell_payment_info_cubit.dart'; +import 'package:realunit_wallet/screens/buy/widgets/payment_action_required.dart'; import 'package:realunit_wallet/screens/sell/cubits/sell_selected_bank_account/sell_selected_bank_account_cubit.dart'; import 'package:realunit_wallet/screens/sell/widgets/sell_bank_account_field.dart'; import 'package:realunit_wallet/screens/sell/widgets/sell_button.dart'; @@ -92,6 +93,12 @@ class _SellViewState extends State { amountController: _amountController, resultController: _resultController, ), + if (state.priceUnavailable) + PaymentActionRequired( + title: S.of(context).priceSourceUnavailableTitle, + description: + S.of(context).priceSourceUnavailableDescription, + ), const SellBankAccountField(), const Spacer(), SellButton( diff --git a/test/screens/buy/cubits/buy_converter_cubit_test.dart b/test/screens/buy/cubits/buy_converter_cubit_test.dart index 53ecc718..a5af0af9 100644 --- a/test/screens/buy/cubits/buy_converter_cubit_test.dart +++ b/test/screens/buy/cubits/buy_converter_cubit_test.dart @@ -86,6 +86,32 @@ void main() { expect(cubit.state.loading, isFalse); }); + test('flags priceUnavailable on service error and clears it on the next success', () async { + // Aktionariat/brokerbot down: every price call fails → the converter + // surfaces a hint via state.priceUnavailable. When the backend recovers + // the next successful call must clear the flag again. + when( + () => service.getBuyShares(any(), any()), + ).thenAnswer((_) async => throw Exception('priceInCHF undefined')); + + final cubit = BuyConverterCubit(service); + await cubit.onFiatChanged('100'); + await Future.delayed(const Duration(milliseconds: 250)); + expect(cubit.state.priceUnavailable, isTrue); + + when(() => service.getBuyShares(any(), any())).thenAnswer( + (_) async => BrokerbotBuySharesDto( + shares: 7, + pricePerShare: 12.5, + availableShares: 100, + ), + ); + await cubit.onFiatChanged('200'); + await Future.delayed(const Duration(milliseconds: 250)); + expect(cubit.state.priceUnavailable, isFalse); + expect(cubit.state.sharesText, '7'); + }); + test( 'onSharesChanged debounces, then writes the converted fiat with matching fractional digits', () async { diff --git a/test/screens/sell/cubits/sell_converter_cubit_test.dart b/test/screens/sell/cubits/sell_converter_cubit_test.dart index 15ac9c0f..5aa81b5c 100644 --- a/test/screens/sell/cubits/sell_converter_cubit_test.dart +++ b/test/screens/sell/cubits/sell_converter_cubit_test.dart @@ -52,6 +52,32 @@ void main() { verify(() => service.getSellShares('100', Currency.chf)).called(1); }); + test('flags priceUnavailable on service error and clears it on the next success', () async { + // Aktionariat/brokerbot down: getSellShares fails → state.priceUnavailable + // drives the hint; a later successful call must clear it again. + when( + () => service.getSellShares(any(), any()), + ).thenAnswer((_) async => throw Exception('priceInCHF undefined')); + + final cubit = SellConverterCubit(service); + await cubit.onFiatChanged('100'); + await Future.delayed(const Duration(milliseconds: 250)); + expect(cubit.state.priceUnavailable, isTrue); + + when(() => service.getSellShares(any(), any())).thenAnswer( + (_) async => BrokerbotSellSharesDto( + targetAmount: 100, + shares: 8, + pricePerShare: 12.5, + currency: 'CHF', + ), + ); + await cubit.onFiatChanged('200'); + await Future.delayed(const Duration(milliseconds: 250)); + expect(cubit.state.priceUnavailable, isFalse); + expect(cubit.state.sharesText, '8'); + }); + test('onFiatChanged respects an explicit currency argument', () async { when(() => service.getSellShares(any(), any())).thenAnswer( (_) async => BrokerbotSellSharesDto(