Added text to milestone page, Updated widget tests

main
henryhdr 2024-06-15 13:35:59 +02:00
parent daa79922ea
commit 825d82212f
8 changed files with 400 additions and 180 deletions

View File

@ -1,7 +1,7 @@
{ {
"title": "Zinseszinsrechner", "title": "Zinseszinsrechner",
"language_english": "Englisch", "language_english": "Englisch/USD",
"language_german": "Deutsch", "language_german": "Deutsch/EUR",
"choose_language": "Sprache auswählen", "choose_language": "Sprache auswählen",
"initial_capital": "Anfangskapital", "initial_capital": "Anfangskapital",
"initial_capital_tooltiptext": "Das Anfangskapital ist der Betrag, den Sie zu Beginn Ihrer Anlage haben.", "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": "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": { "@result_text": {
"investment_period": { "investment_period": {
"type" : "int" "type": "String"
}, },
"monthly_savings_rate": { "monthly_savings_rate": {
"type" : "int" "type": "String"
}, },
"interest_rate": { "interest_rate": {
"type" : "int" "type": "String"
}, },
"final_capital": { "final_capital": {
"type" : "int" "type": "int"
}, },
"invested_money": { "invested_money": {
"type" : "int" "type": "String"
}, },
"compound_interest": { "compound_interest": {
"type" : "int" "type": "String"
} }
}, },
"graphic": "Grafik", "graphic": "Grafik",
@ -57,5 +57,22 @@
"payout_interval": "Ausschüttungsintervall", "payout_interval": "Ausschüttungsintervall",
"payout_interval_tooltiptext": "Das Ausschüttungsintervall bezieht sich darauf, wie oft die Erträge aus Ihrer Investition ausgeschüttet werden.", "payout_interval_tooltiptext": "Das Ausschüttungsintervall bezieht sich darauf, wie oft die Erträge aus Ihrer Investition ausgeschüttet werden.",
"year": "Jahr", "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."
} }

View File

@ -1,7 +1,7 @@
{ {
"title": "Compound interest calculator", "title": "Compound interest calculator",
"language_english": "English", "language_english": "English/USD",
"language_german": "German", "language_german": "German/EUR",
"choose_language": "Choose language", "choose_language": "Choose language",
"initial_capital": "Initial capital", "initial_capital": "Initial capital",
"initial_capital_tooltiptext": "The initial capital is the amount you have at the start of your investment.", "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": "Payout interval",
"payout_interval_tooltiptext": "The payout interval refers to how often the income from your investment is distributed.", "payout_interval_tooltiptext": "The payout interval refers to how often the income from your investment is distributed.",
"year": "Year", "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."
} }

View File

@ -46,7 +46,7 @@ class MilestonePage extends StatelessWidget {
SliverToBoxAdapter( SliverToBoxAdapter(
child: Padding( child: Padding(
padding: const EdgeInsets.all(10.0), 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()),
), ),
), ),
], ],

View File

@ -60,6 +60,7 @@ class LanguageSwitcherState extends State<LanguageSwitcher> {
], ],
), ),
child: PopupMenuButton<Locale>( child: PopupMenuButton<Locale>(
key: const Key('languagePopupMenu'),
offset: Offset(0.0, isMobile ? 55 : 45), offset: Offset(0.0, isMobile ? 55 : 45),
onSelected: (Locale locale) { onSelected: (Locale locale) {
setState(() { setState(() {

View File

@ -1,9 +1,9 @@
import 'package:flutter/cupertino.dart'; 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 { class MilestoneTimeline extends StatelessWidget {
final List<Map<String, dynamic>> milestones; // Liste der Meilensteine final List<Map<String, dynamic>> milestones;
final double totalInterest; // Gesamter Zinsertrag final int totalInterest;
const MilestoneTimeline({ const MilestoneTimeline({
super.key, super.key,
@ -13,20 +13,25 @@ class MilestoneTimeline extends StatelessWidget {
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
return SizedBox( final localizations = AppLocalizations.of(context)!;
return Row(
children: [
Expanded(
child: Center( child: Center(
child: SingleChildScrollView(
child: Column( child: Column(
mainAxisAlignment: MainAxisAlignment.center, mainAxisAlignment: MainAxisAlignment.center,
children: List.generate(milestones.length, (index) { children: List.generate(milestones.length, (index) {
double milestoneValue = milestones[index]['value']; // Der Wert des aktuellen Meilensteins double milestoneValue = milestones[index]['value'];
String milestoneEmoji = milestones[index]['emoji']; // Das Emoji des aktuellen Meilensteins String milestoneEmoji = milestones[index]['emoji'];
String milestoneText = milestones[index]['text']; // Der Text des aktuellen Meilensteins String milestoneText = milestones[index]['text'];
bool milestoneReached = totalInterest >= milestoneValue; // Gibt an, ob der Meilenstein erreicht wurde bool milestoneReached = totalInterest >= milestoneValue;
// Widget für den aktuellen Meilenstein return Padding(
return Column( padding: const EdgeInsets.symmetric(vertical: 8.0),
child: Column(
children: [ children: [
// Trennlinie zwischen den Meilensteinen, außer für den ersten Meilenstein
if (index > 0) if (index > 0)
Container( Container(
height: 25, height: 25,
@ -49,7 +54,6 @@ class MilestoneTimeline extends StatelessWidget {
], ],
), ),
), ),
// Widget für das Symbol (Kreis oder Häkchen) je nachdem, ob der Meilenstein erreicht wurde
Padding( Padding(
padding: const EdgeInsets.symmetric(vertical: 4.0), padding: const EdgeInsets.symmetric(vertical: 4.0),
child: Icon( child: Icon(
@ -59,10 +63,85 @@ class MilestoneTimeline extends StatelessWidget {
), ),
), ),
], ],
),
); );
}), }),
), ),
), ),
),
),
Expanded(
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Container(
width: double.infinity,
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
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 _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),
),
),
],
),
); );
} }
} }

View File

@ -48,12 +48,12 @@ class ResultWidget extends StatelessWidget {
children: [ children: [
Expanded(child: _buildResultBox(localizations.final_capital, localizations.amount(compoundInterest))), Expanded(child: _buildResultBox(localizations.final_capital, localizations.amount(compoundInterest))),
Expanded(child: _buildResultBox(localizations.deposits, localizations.amount(investedMoney))), 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), const SizedBox(height: 10),
Text( 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), const SizedBox(height: 20),
CustomImageButton( CustomImageButton(

View File

@ -2,6 +2,7 @@ import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter_application_1/pages/chart_page.dart'; import 'package:flutter_application_1/pages/chart_page.dart';
import 'package:flutter_application_1/pages/milestone_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_gen/gen_l10n/app_localizations.dart';
import 'package:flutter_localizations/flutter_localizations.dart'; import 'package:flutter_localizations/flutter_localizations.dart';
import 'package:flutter_application_1/enums.dart'; import 'package:flutter_application_1/enums.dart';
@ -45,7 +46,6 @@ void main() {
expect(find.byType(CupertinoTextField), findsOneWidget); expect(find.byType(CupertinoTextField), findsOneWidget);
expect(find.byType(Tooltip), findsOneWidget); expect(find.byType(Tooltip), findsOneWidget);
}); });
testWidgets('Displays validation icon correctly', (WidgetTester tester) async { testWidgets('Displays validation icon correctly', (WidgetTester tester) async {
final controller = TextEditingController(); final controller = TextEditingController();
final focusNode = FocusNode(); final focusNode = FocusNode();
@ -82,7 +82,6 @@ void main() {
expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsOneWidget); expect(find.byIcon(CupertinoIcons.check_mark_circled_solid), findsOneWidget);
expect(find.byIcon(CupertinoIcons.clear_circled_solid), findsNothing); expect(find.byIcon(CupertinoIcons.clear_circled_solid), findsNothing);
}); });
testWidgets('Text input updates controller', (WidgetTester tester) async { testWidgets('Text input updates controller', (WidgetTester tester) async {
final controller = TextEditingController(); final controller = TextEditingController();
final focusNode = FocusNode(); final focusNode = FocusNode();
@ -148,31 +147,103 @@ void main() {
}); });
}); });
group('Milestone Timeline Widget Tests', () { group('Milestone Timeline Widget Tests', () {
testWidgets('Milestone timeline displays correctly', (WidgetTester tester) async { testWidgets('Shows timeline and and some milestones were reached', (WidgetTester tester) async {
final List<Map<String, dynamic>> milestones = [ const Locale testLocale = Locale('en');
{'value': 100.0, 'emoji': '😊', 'text': 'First milestone'}, final localizations = await AppLocalizations.delegate.load(testLocale);
{'value': 200.0, 'emoji': '😃', 'text': 'Second milestone'},
{'value': 300.0, 'emoji': '😁', 'text': 'Third milestone'}, List<Map<String, dynamic>> 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 double totalInterest = 250.0;
const int totalInterest = 5000;
await tester.pumpWidget( await tester.pumpWidget(
CupertinoApp( MaterialApp(
locale: testLocale,
localizationsDelegates: const [
AppLocalizations.delegate,
GlobalMaterialLocalizations.delegate,
GlobalWidgetsLocalizations.delegate,
GlobalCupertinoLocalizations.delegate,
],
home: Material( home: Material(
child: MilestoneTimeline( child: MilestoneTimeline(
milestones: milestones, milestones: milestoneList,
totalInterest: totalInterest, totalInterest: totalInterest,
), ),
), ),
), ),
); );
expect(find.text('First milestone'), findsOneWidget); expect(find.text(localizations.milestone_text1 + localizations.amount(700)), findsOneWidget);
expect(find.text('Second milestone'), findsOneWidget); expect(find.text(localizations.milestone_text2 + localizations.amount(3250)), findsOneWidget);
expect(find.text('Third milestone'), 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.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<Map<String, dynamic>> 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', () { group('Result Widget Tests', () {
@ -218,10 +289,10 @@ void main() {
expect(find.text(localizations.deposits), findsOneWidget); expect(find.text(localizations.deposits), findsOneWidget);
expect(find.text(localizations.amount(investedMoney)), findsOneWidget); expect(find.text(localizations.amount(investedMoney)), findsOneWidget);
expect(find.text(localizations.interest_received), 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( final resultText = localizations.result_text(
double.parse(compoundInterest) - double.parse(investedMoney), (double.parse(compoundInterest) - double.parse(investedMoney)).round(),
compoundInterest, compoundInterest,
interestRate, interestRate,
investedMoney, investedMoney,
@ -234,7 +305,6 @@ void main() {
expect(find.text(localizations.milestones), findsOneWidget); expect(find.text(localizations.milestones), findsOneWidget);
}); });
}); });
group('Error Widget Tests', () { group('Error Widget Tests', () {
testWidgets('Displays error message correctly', (WidgetTester tester) async { testWidgets('Displays error message correctly', (WidgetTester tester) async {
const String errorMessage = 'This is an error message'; const String errorMessage = 'This is an error message';
@ -253,6 +323,52 @@ void main() {
expect(find.text(errorMessage), findsOneWidget); 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', () { group('Chart Page Tests', () {
testWidgets('Shows correct localized texts and table data', (WidgetTester tester) async { testWidgets('Shows correct localized texts and table data', (WidgetTester tester) async {
const Locale testLocale = Locale('en'); const Locale testLocale = Locale('en');