diff --git a/lib/l10n/app_de.arb b/lib/l10n/app_de.arb index a2a206f..42f847f 100644 --- a/lib/l10n/app_de.arb +++ b/lib/l10n/app_de.arb @@ -1,7 +1,7 @@ { "title": "Zinseszinsrechner", - "language_english": "Englisch", - "language_german": "Deutsch", + "language_english": "Englisch/USD", + "language_german": "Deutsch/EUR", "choose_language": "Sprache auswählen", "initial_capital": "Anfangskapital", "initial_capital_tooltiptext": "Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.", @@ -34,22 +34,22 @@ "result_text": "Wenn du über einen Zeitraum von {investment_period} Jahren monatlich {monthly_savings_rate}€ mit einem Zinssatz von {interest_rate}% investierst, erreichst du am Ende ein Endkapital von {final_capital}€. Dieses setzt sich aus Einzahlungen von {invested_money}€ und Zinsen bzw. Kapitalerträgen in Höhe von {compound_interest}€ zusammen.", "@result_text": { "investment_period": { - "type" : "int" + "type": "String" }, "monthly_savings_rate": { - "type" : "int" + "type": "String" }, "interest_rate": { - "type" : "int" + "type": "String" }, "final_capital": { - "type" : "int" + "type": "int" }, "invested_money": { - "type" : "int" + "type": "String" }, "compound_interest": { - "type" : "int" + "type": "String" } }, "graphic": "Grafik", @@ -57,5 +57,22 @@ "payout_interval": "Ausschüttungsintervall", "payout_interval_tooltiptext": "Das Ausschüttungsintervall bezieht sich darauf, wie oft die Erträge aus Ihrer Investition ausgeschüttet werden.", "year": "Jahr", - "currency_written_out": "Euro" + "currency_written_out": "Euro", + "what_you_can_afford": "Herzlichen Glückwunsch! Mit Hilfe des Zinseszinsrechners hast du deine Investitionen und deren erzielte Zinsen genau im Blick behalten. Diese kluge finanzielle Planung ermöglicht es dir, mehr zu tun, als du vielleicht erwartet hast. Mit den erwirtschafteten Zinsen in Höhe von {interest_received} könntest du zum Beispiel:", + "@what_you_can_afford": { + "interest_received": { + "type": "int" + } + }, + "what_you_cant_afford": "Herzlichen Glückwunsch! Mit Hilfe des Zinseszinsrechners hast du deine Investitionen und deren erzielte Zinsen genau im Blick behalten. Diese kluge finanzielle Planung legt den Grundstein für deine zukünftigen finanziellen Ziele. Auch wenn du bisher keinen der Meilensteine erreicht hast, bleib dran! Deine erwirtschafteten Zinsen in Höhe von {interest_received} eröffnen dir weiterhin einige Möglichkeiten.", + "@what_you_cant_afford": { + "interest_received": { + "type": "int" + } + }, + "smartphone": "Ein aktuelles Smartphone kaufen.", + "ebike": "Ein hochwertiges eBike anschaffen.", + "world_tour": "Eine unvergessliche Weltreise planen.", + "sports_car": "Einen luxuriösen Sportwagen erwerben.", + "single_family_house": "Ein geräumiges Einfamilienhaus mit 150 qm Wohnfläche zu kaufen." } \ No newline at end of file diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 99806a5..8a081cd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -1,7 +1,7 @@ { "title": "Compound interest calculator", - "language_english": "English", - "language_german": "German", + "language_english": "English/USD", + "language_german": "German/EUR", "choose_language": "Choose language", "initial_capital": "Initial capital", "initial_capital_tooltiptext": "The initial capital is the amount you have at the start of your investment.", @@ -32,5 +32,12 @@ "payout_interval": "Payout interval", "payout_interval_tooltiptext": "The payout interval refers to how often the income from your investment is distributed.", "year": "Year", - "currency_written_out": "Dollar" + "currency_written_out": "Dollar", + "what_you_can_afford": "Congratulations! With the help of the compound interest calculator, you can keep a close eye on your investments and the interest earned on them. This smart financial planning allows you to do more than you might have expected. With the earned interest of {interest_received} you could, for example:", + "what_you_cant_afford": "Congratulations! With the help of the compound interest calculator, you can keep a close eye on your investments and the interest earned on them. This smart financial planning lays the foundation for your future financial goals. Even if you haven't reached any of the milestones yet, stick with it! Your earned interest of {interest_received} continues to open up a number of opportunities for you.", + "smartphone": "Buy a current smartphone.", + "ebike": "Buy a high-quality eBike.", + "world_tour": "Plan an unforgettable trip around the world.", + "sports_car": "Buy a luxurious sports car.", + "single_family_house": "To buy a spacious single-family home with 150 square meters of living space." } \ No newline at end of file diff --git a/lib/main.dart b/lib/main.dart index 8c41c44..8327fbf 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -198,117 +198,117 @@ class _MyHomePageState extends State { return Scaffold( body: SafeArea( child: Stack( - children: [ - SingleChildScrollView( - physics: const BouncingScrollPhysics(), - child: Padding( - padding: const EdgeInsets.all(20.0), - child: Column( - crossAxisAlignment: CrossAxisAlignment.stretch, - children: [ - InputWidget( - label: localizations.initial_capital, - controller: _initialCapitalController, - focusNode: _initialCapitalFocusNode, - isValid: _isInitialCapitalEntered, - suffixText: localizations.currency, - tooltipText: localizations.initial_capital_tooltiptext - ), - InputWidget( - label: localizations.monthly_savings_rate, - controller: _monthlySavingsRateController, - focusNode: _monthlySavingsRateFocusNode, - isValid: _isMonthlySavingsRateEntered, - suffixText: localizations.currency, - tooltipText: localizations.monthly_savings_rate_tooltiptext - ), - InputWidget( - label: localizations.interest_rate, - controller: _interestRateController, - focusNode: _interestRateFocusNode, - isValid: _isInterestRateEntered, - suffixText: '%', - tooltipText: localizations.interest_rate_tooltiptext - ), - InputWidget( - label: localizations.investment_period, - controller: _timeController, - focusNode: _timeFocusNode, - isValid: _isTimeEntered, - suffixText: localizations.years, - tooltipText: localizations.investment_period_tooltiptext - ), - IntervalWidget( - selectedInterval: _payoutInterval == PayoutInterval.yearly ? localizations.yearly : localizations.monthly, - onChanged: (newInterval) { - setState(() { - _payoutInterval = newInterval == localizations.yearly ? PayoutInterval.yearly : PayoutInterval.monthly; - }); - }, - ), - ElevatedButton( - onPressed: () { - if (_isInitialCapitalEntered && - _isMonthlySavingsRateEntered && - _isInterestRateEntered && - _isTimeEntered) { - setInitialCapital(); - setMonthlySavingsRate(); - setInterestRate(); - setTime(); - _investedMoney = calculateInvestedMoney(_initialCapital, _monthlySavingsRate, _time, _investedMoneyList); - _compoundInterest = calculateCompoundInterest( - _initialCapital, - _monthlySavingsRate, - _interestRate, - _time, - _payoutInterval, - _investedMoneyList, - _compoundInterestList - ); - _isCalculated = CalculationPerformed.yes; - } else { - _isCalculated = CalculationPerformed.no; - } - setState(() {}); - }, - style: ButtonStyle( - backgroundColor: MaterialStateProperty.all(CupertinoColors.black), - foregroundColor: MaterialStateProperty.all(CupertinoColors.white), - shape: MaterialStateProperty.all( - RoundedRectangleBorder( - borderRadius: BorderRadius.circular(5), + children: [ + SingleChildScrollView( + physics: const BouncingScrollPhysics(), + child: Padding( + padding: const EdgeInsets.all(20.0), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + InputWidget( + label: localizations.initial_capital, + controller: _initialCapitalController, + focusNode: _initialCapitalFocusNode, + isValid: _isInitialCapitalEntered, + suffixText: localizations.currency, + tooltipText: localizations.initial_capital_tooltiptext + ), + InputWidget( + label: localizations.monthly_savings_rate, + controller: _monthlySavingsRateController, + focusNode: _monthlySavingsRateFocusNode, + isValid: _isMonthlySavingsRateEntered, + suffixText: localizations.currency, + tooltipText: localizations.monthly_savings_rate_tooltiptext + ), + InputWidget( + label: localizations.interest_rate, + controller: _interestRateController, + focusNode: _interestRateFocusNode, + isValid: _isInterestRateEntered, + suffixText: '%', + tooltipText: localizations.interest_rate_tooltiptext + ), + InputWidget( + label: localizations.investment_period, + controller: _timeController, + focusNode: _timeFocusNode, + isValid: _isTimeEntered, + suffixText: localizations.years, + tooltipText: localizations.investment_period_tooltiptext + ), + IntervalWidget( + selectedInterval: _payoutInterval == PayoutInterval.yearly ? localizations.yearly : localizations.monthly, + onChanged: (newInterval) { + setState(() { + _payoutInterval = newInterval == localizations.yearly ? PayoutInterval.yearly : PayoutInterval.monthly; + }); + }, + ), + ElevatedButton( + onPressed: () { + if (_isInitialCapitalEntered && + _isMonthlySavingsRateEntered && + _isInterestRateEntered && + _isTimeEntered) { + setInitialCapital(); + setMonthlySavingsRate(); + setInterestRate(); + setTime(); + _investedMoney = calculateInvestedMoney(_initialCapital, _monthlySavingsRate, _time, _investedMoneyList); + _compoundInterest = calculateCompoundInterest( + _initialCapital, + _monthlySavingsRate, + _interestRate, + _time, + _payoutInterval, + _investedMoneyList, + _compoundInterestList + ); + _isCalculated = CalculationPerformed.yes; + } else { + _isCalculated = CalculationPerformed.no; + } + setState(() {}); + }, + style: ButtonStyle( + backgroundColor: MaterialStateProperty.all(CupertinoColors.black), + foregroundColor: MaterialStateProperty.all(CupertinoColors.white), + shape: MaterialStateProperty.all( + RoundedRectangleBorder( + borderRadius: BorderRadius.circular(5), + ), + ), + ), + child: Text( + localizations.calculate, + style: const TextStyle( + fontWeight: FontWeight.bold, + ), ), ), - ), - child: Text( - localizations.calculate, - style: const TextStyle( - fontWeight: FontWeight.bold, - ), - ), + const SizedBox(height: 20), + if(_isCalculated == CalculationPerformed.yes) + ResultWidget( + compoundInterest: _compoundInterest.toStringAsFixed(0), + investedMoney: _investedMoney.toStringAsFixed(0), + time: _time.toStringAsFixed(0), + monthlySavingsRate: _monthlySavingsRate.toStringAsFixed(0), + interestRate: _interestRate.toStringAsFixed(0), + payoutInterval: _payoutInterval, + investedMoneyList: _investedMoneyList, + compoundInterestList: _compoundInterestList, + ), + if(_isCalculated == CalculationPerformed.no) + ErrWidget( + errorMessage: localizations.invalid_input, + ), + ], ), - const SizedBox(height: 20), - if(_isCalculated == CalculationPerformed.yes) - ResultWidget( - compoundInterest: _compoundInterest.toStringAsFixed(0), - investedMoney: _investedMoney.toStringAsFixed(0), - time: _time.toStringAsFixed(0), - monthlySavingsRate: _monthlySavingsRate.toStringAsFixed(0), - interestRate: _interestRate.toStringAsFixed(0), - payoutInterval: _payoutInterval, - investedMoneyList: _investedMoneyList, - compoundInterestList: _compoundInterestList, - ), - if(_isCalculated == CalculationPerformed.no) - ErrWidget( - errorMessage: localizations.invalid_input, - ), - ], + ), ), - ), - ), - Positioned( + Positioned( top: MediaQuery.of(context).size.height / 2 - 20, right: 10, child: LanguageSwitcher( diff --git a/lib/pages/milestone_page.dart b/lib/pages/milestone_page.dart index 0c55bbf..b12f499 100644 --- a/lib/pages/milestone_page.dart +++ b/lib/pages/milestone_page.dart @@ -46,7 +46,7 @@ class MilestonePage extends StatelessWidget { SliverToBoxAdapter( child: Padding( padding: const EdgeInsets.all(10.0), - child: MilestoneTimeline(milestones: milestoneList, totalInterest: double.parse(compoundInterest) - double.parse(investedMoney)), + child: MilestoneTimeline(milestones: milestoneList, totalInterest: (double.parse(compoundInterest) - double.parse(investedMoney)).round()), ), ), ], diff --git a/lib/widgets/language_switcher_widget.dart b/lib/widgets/language_switcher_widget.dart index cdaa287..663cfab 100644 --- a/lib/widgets/language_switcher_widget.dart +++ b/lib/widgets/language_switcher_widget.dart @@ -60,6 +60,7 @@ class LanguageSwitcherState extends State { ], ), child: PopupMenuButton( + key: const Key('languagePopupMenu'), offset: Offset(0.0, isMobile ? 55 : 45), onSelected: (Locale locale) { setState(() { diff --git a/lib/widgets/milestone_timeline_widget.dart b/lib/widgets/milestone_timeline_widget.dart index 2b1beba..88258d0 100644 --- a/lib/widgets/milestone_timeline_widget.dart +++ b/lib/widgets/milestone_timeline_widget.dart @@ -1,67 +1,146 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter_gen/gen_l10n/app_localizations.dart'; -// Widget einer Meilenstein-Timeline basierend auf den übergebenen Meilensteinen und dem gesamten Zinsertrag class MilestoneTimeline extends StatelessWidget { - final List> milestones; // Liste der Meilensteine - final double totalInterest; // Gesamter Zinsertrag + final List> milestones; + final int totalInterest; const MilestoneTimeline({ - super.key, + super.key, required this.milestones, required this.totalInterest, }); @override Widget build(BuildContext context) { - return SizedBox( - child: Center( - child: Column( - mainAxisAlignment: MainAxisAlignment.center, - children: List.generate(milestones.length, (index) { - double milestoneValue = milestones[index]['value']; // Der Wert des aktuellen Meilensteins - String milestoneEmoji = milestones[index]['emoji']; // Das Emoji des aktuellen Meilensteins - String milestoneText = milestones[index]['text']; // Der Text des aktuellen Meilensteins - bool milestoneReached = totalInterest >= milestoneValue; // Gibt an, ob der Meilenstein erreicht wurde + final localizations = AppLocalizations.of(context)!; - // Widget für den aktuellen Meilenstein - return Column( + return Row( + children: [ + Expanded( + child: Center( + child: SingleChildScrollView( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: List.generate(milestones.length, (index) { + double milestoneValue = milestones[index]['value']; + String milestoneEmoji = milestones[index]['emoji']; + String milestoneText = milestones[index]['text']; + bool milestoneReached = totalInterest >= milestoneValue; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Column( + children: [ + if (index > 0) + Container( + height: 25, + width: 2, + color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black, + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Column( + children: [ + Text( + milestoneEmoji, + style: const TextStyle(fontSize: 30), + ), + const SizedBox(height: 5), + Text( + milestoneText, + textAlign: TextAlign.center, + ), + ], + ), + ), + Padding( + padding: const EdgeInsets.symmetric(vertical: 4.0), + child: Icon( + milestoneReached ? CupertinoIcons.check_mark_circled_solid : CupertinoIcons.circle_fill, + size: 20, + color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black, + ), + ), + ], + ), + ); + }), + ), + ), + ), + ), + Expanded( + child: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, children: [ - // Trennlinie zwischen den Meilensteinen, außer für den ersten Meilenstein - if (index > 0) - Container( - height: 25, - width: 2, - color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black, + Container( + width: double.infinity, + padding: const EdgeInsets.all(16.0), + decoration: BoxDecoration( + borderRadius: BorderRadius.circular(8.0), ), - Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), child: Column( + crossAxisAlignment: CrossAxisAlignment.start, children: [ Text( - milestoneEmoji, - style: const TextStyle(fontSize: 30), - ), - const SizedBox(height: 5), - Text( - milestoneText, + totalInterest < milestones[0]['value'] ? localizations.what_you_cant_afford(localizations.amount(totalInterest)) : localizations.what_you_can_afford(localizations.amount(totalInterest)), + style: const TextStyle(fontWeight: FontWeight.bold), textAlign: TextAlign.center, ), + const SizedBox(height: 10), + Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (totalInterest >= milestones[0]['value']) ...[ + _buildListItem(context, localizations.smartphone), + ], + if (totalInterest >= milestones[1]['value']) ...[ + _buildListItem(context, localizations.ebike), + ], + if (totalInterest >= milestones[2]['value']) ...[ + _buildListItem(context, localizations.world_tour), + ], + if (totalInterest >= milestones[3]['value']) ...[ + _buildListItem(context, localizations.sports_car), + ], + if (totalInterest >= milestones[4]['value']) ...[ + _buildListItem(context, localizations.single_family_house), + ], + ], + ), ], ), ), - // Widget für das Symbol (Kreis oder Häkchen) je nachdem, ob der Meilenstein erreicht wurde - Padding( - padding: const EdgeInsets.symmetric(vertical: 4.0), - child: Icon( - milestoneReached ? CupertinoIcons.check_mark_circled_solid : CupertinoIcons.circle_fill, - size: 20, - color: milestoneReached ? CupertinoColors.systemGreen : CupertinoColors.black, - ), - ), ], - ); - }), + ), + ), ), + ], + ); + } + + Widget _buildListItem(BuildContext context, String text) { + return Padding( + padding: const EdgeInsets.symmetric(vertical: 8.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + const Icon( + CupertinoIcons.circle_fill, + size: 12, + color: CupertinoColors.black, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + text, + textAlign: TextAlign.start, + style: const TextStyle(fontWeight: FontWeight.bold), + ), + ), + ], ), ); } diff --git a/lib/widgets/result_widget.dart b/lib/widgets/result_widget.dart index 1ed58fa..e133399 100644 --- a/lib/widgets/result_widget.dart +++ b/lib/widgets/result_widget.dart @@ -48,12 +48,12 @@ class ResultWidget extends StatelessWidget { children: [ Expanded(child: _buildResultBox(localizations.final_capital, localizations.amount(compoundInterest))), Expanded(child: _buildResultBox(localizations.deposits, localizations.amount(investedMoney))), - Expanded(child: _buildResultBox(localizations.interest_received, localizations.amount(double.parse(compoundInterest) - double.parse(investedMoney)))), + Expanded(child: _buildResultBox(localizations.interest_received, localizations.amount((double.parse(compoundInterest) - double.parse(investedMoney)).round()))), ], ), const SizedBox(height: 10), Text( - localizations.result_text(double.parse(compoundInterest) - double.parse(investedMoney), compoundInterest, interestRate, investedMoney, time, monthlySavingsRate) + localizations.result_text((double.parse(compoundInterest) - double.parse(investedMoney)).round(), compoundInterest, interestRate, investedMoney, time, monthlySavingsRate) ), const SizedBox(height: 20), CustomImageButton( diff --git a/test/widget_test.dart b/test/widget_test.dart index 226923b..1dfc50c 100644 --- a/test/widget_test.dart +++ b/test/widget_test.dart @@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_application_1/pages/chart_page.dart'; import 'package:flutter_application_1/pages/milestone_page.dart'; +import 'package:flutter_application_1/widgets/language_switcher_widget.dart'; import 'package:flutter_gen/gen_l10n/app_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_application_1/enums.dart'; @@ -45,7 +46,6 @@ void main() { expect(find.byType(CupertinoTextField), findsOneWidget); expect(find.byType(Tooltip), findsOneWidget); }); - testWidgets('Displays validation icon correctly', (WidgetTester tester) async { final controller = TextEditingController(); final focusNode = FocusNode(); @@ -82,7 +82,6 @@ void main() { expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsOneWidget); expect(find.byIcon(CupertinoIcons.clear_circled_solid), findsNothing); }); - testWidgets('Text input updates controller', (WidgetTester tester) async { final controller = TextEditingController(); final focusNode = FocusNode(); @@ -148,31 +147,103 @@ void main() { }); }); group('Milestone Timeline Widget Tests', () { - testWidgets('Milestone timeline displays correctly', (WidgetTester tester) async { - final List> milestones = [ - {'value': 100.0, 'emoji': '😊', 'text': 'First milestone'}, - {'value': 200.0, 'emoji': '😃', 'text': 'Second milestone'}, - {'value': 300.0, 'emoji': '😁', 'text': 'Third milestone'}, - ]; - const double totalInterest = 250.0; + testWidgets('Shows timeline and and some milestones were reached', (WidgetTester tester) async { + const Locale testLocale = Locale('en'); + final localizations = await AppLocalizations.delegate.load(testLocale); + + List> milestoneList = [ + {'value': 700.0, 'emoji': '📱', 'text': localizations.milestone_text1 + localizations.amount(700)}, + {'value': 3250.0, 'emoji': '🚲', 'text': localizations.milestone_text2 + localizations.amount(3250)}, + {'value': 20000.0, 'emoji': '🌎', 'text': localizations.milestone_text3 + localizations.amount(20000)}, + {'value': 100000.0, 'emoji': '🏎️', 'text': localizations.milestone_text4 + localizations.amount(100000)}, + {'value': 350000.0, 'emoji': '🏡', 'text': localizations.milestone_text5 + localizations.amount(350000)}, + ]; + + const int totalInterest = 5000; await tester.pumpWidget( - CupertinoApp( + MaterialApp( + locale: testLocale, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], home: Material( child: MilestoneTimeline( - milestones: milestones, + milestones: milestoneList, totalInterest: totalInterest, ), ), ), ); - expect(find.text('First milestone'), findsOneWidget); - expect(find.text('Second milestone'), findsOneWidget); - expect(find.text('Third milestone'), findsOneWidget); + expect(find.text(localizations.milestone_text1 + localizations.amount(700)), findsOneWidget); + expect(find.text(localizations.milestone_text2 + localizations.amount(3250)), findsOneWidget); + expect(find.text(localizations.milestone_text3 + localizations.amount(20000)), findsOneWidget); + expect(find.text(localizations.milestone_text4 + localizations.amount(100000)), findsOneWidget); + expect(find.text(localizations.milestone_text5 + localizations.amount(350000)), findsOneWidget); expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsNWidgets(2)); - expect(find.byIcon(CupertinoIcons.circle_fill), findsOneWidget); + + expect(find.text(localizations.what_you_cant_afford(localizations.amount(totalInterest))), findsNothing); + expect(find.text(localizations.what_you_can_afford(localizations.amount(totalInterest))), findsOneWidget); + + expect(find.text(localizations.smartphone), findsOneWidget); + expect(find.text(localizations.ebike), findsOneWidget); + expect(find.text(localizations.world_tour), findsNothing); + expect(find.text(localizations.sports_car), findsNothing); + expect(find.text(localizations.single_family_house), findsNothing); + }); + testWidgets('Shows timeline but no milestone was reached', (WidgetTester tester) async { + const Locale testLocale = Locale('en'); + final localizations = await AppLocalizations.delegate.load(testLocale); + + List> milestoneList = [ + {'value': 700.0, 'emoji': '📱', 'text': localizations.milestone_text1 + localizations.amount(700)}, + {'value': 3250.0, 'emoji': '🚲', 'text': localizations.milestone_text2 + localizations.amount(3250)}, + {'value': 20000.0, 'emoji': '🌎', 'text': localizations.milestone_text3 + localizations.amount(20000)}, + {'value': 100000.0, 'emoji': '🏎️', 'text': localizations.milestone_text4 + localizations.amount(100000)}, + {'value': 350000.0, 'emoji': '🏡', 'text': localizations.milestone_text5 + localizations.amount(350000)}, + ]; + + const int totalInterest = 0; + + await tester.pumpWidget( + MaterialApp( + locale: testLocale, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + home: Material( + child: MilestoneTimeline( + milestones: milestoneList, + totalInterest: totalInterest, + ), + ), + ), + ); + + expect(find.text(localizations.milestone_text1 + localizations.amount(700)), findsOneWidget); + expect(find.text(localizations.milestone_text2 + localizations.amount(3250)), findsOneWidget); + expect(find.text(localizations.milestone_text3 + localizations.amount(20000)), findsOneWidget); + expect(find.text(localizations.milestone_text4 + localizations.amount(100000)), findsOneWidget); + expect(find.text(localizations.milestone_text5 + localizations.amount(350000)), findsOneWidget); + + expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsNothing); + + expect(find.text(localizations.what_you_can_afford(localizations.amount(totalInterest))), findsNothing); + expect(find.text(localizations.what_you_cant_afford(localizations.amount(totalInterest))), findsOneWidget); + + expect(find.text(localizations.smartphone), findsNothing); + expect(find.text(localizations.ebike), findsNothing); + expect(find.text(localizations.world_tour), findsNothing); + expect(find.text(localizations.sports_car), findsNothing); + expect(find.text(localizations.single_family_house), findsNothing); }); }); group('Result Widget Tests', () { @@ -218,10 +289,10 @@ void main() { expect(find.text(localizations.deposits), findsOneWidget); expect(find.text(localizations.amount(investedMoney)), findsOneWidget); expect(find.text(localizations.interest_received), findsOneWidget); - expect(find.text(localizations.amount(double.parse(compoundInterest) - double.parse(investedMoney))), findsOneWidget); + expect(find.text(localizations.amount((double.parse(compoundInterest) - double.parse(investedMoney)).round())), findsOneWidget); final resultText = localizations.result_text( - double.parse(compoundInterest) - double.parse(investedMoney), + (double.parse(compoundInterest) - double.parse(investedMoney)).round(), compoundInterest, interestRate, investedMoney, @@ -234,7 +305,6 @@ void main() { expect(find.text(localizations.milestones), findsOneWidget); }); }); - group('Error Widget Tests', () { testWidgets('Displays error message correctly', (WidgetTester tester) async { const String errorMessage = 'This is an error message'; @@ -253,6 +323,52 @@ void main() { expect(find.text(errorMessage), findsOneWidget); }); }); + group('Language Switcher Widget Tests', () { + testWidgets('LanguageSwitcher widget test', (WidgetTester tester) async { + const Locale testLocale = Locale('en'); + final localizations = await AppLocalizations.delegate.load(testLocale); + + Locale selectedLocale = testLocale; + + void handleLocaleChange(Locale locale) { + selectedLocale = locale; + } + + await tester.pumpWidget( + MaterialApp( + locale: testLocale, + localizationsDelegates: const [ + AppLocalizations.delegate, + GlobalMaterialLocalizations.delegate, + GlobalWidgetsLocalizations.delegate, + GlobalCupertinoLocalizations.delegate, + ], + supportedLocales: AppLocalizations.supportedLocales, + home: Scaffold( + body: LanguageSwitcher( + currentLocale: selectedLocale, + onLocaleChanged: handleLocaleChange, + ), + ), + ), + ); + await tester.pumpAndSettle(); + + expect(find.byKey(const Key('languagePopupMenu')), findsOneWidget); + + await tester.tap(find.byKey(const Key('languagePopupMenu'))); + await tester.pumpAndSettle(); + + // Sicherstellen, dass das Ziel-Widget sichtbar ist + await tester.ensureVisible(find.text(localizations.language_german)); + + expect(find.text(localizations.language_english), findsOneWidget); + expect(find.byIcon(CupertinoIcons.checkmark_alt), findsOneWidget); + expect(find.text(localizations.language_german), findsOneWidget); + + await tester.tap(find.text(localizations.language_german)); + }); + }); group('Chart Page Tests', () { testWidgets('Shows correct localized texts and table data', (WidgetTester tester) async { const Locale testLocale = Locale('en');