From 880696ad1841fba8fe315847001e602291a43e7d Mon Sep 17 00:00:00 2001 From: henryhdr Date: Mon, 3 Jun 2024 12:46:40 +0200 Subject: [PATCH] Add initial unit tests; enhance Chart and Milestone widgets --- lib/main.dart | 1 - lib/translations.dart | 10 -- lib/utils.dart | 10 ++ lib/widgets/chart_widget.dart | 153 +++++++++++++++++++++ lib/widgets/interval_widget.dart | 2 +- lib/widgets/milestone_timeline_widget.dart | 41 +++++- lib/widgets/result_widget.dart | 32 ++--- lib/widgets/second_page_widget.dart | 50 ------- test/unit_test.dart | 44 ++++++ test/widget_test.dart | 1 + 10 files changed, 260 insertions(+), 84 deletions(-) delete mode 100644 lib/translations.dart delete mode 100644 lib/widgets/second_page_widget.dart create mode 100644 test/unit_test.dart create mode 100644 test/widget_test.dart diff --git a/lib/main.dart b/lib/main.dart index a46d912..4f6ff57 100644 --- a/lib/main.dart +++ b/lib/main.dart @@ -2,7 +2,6 @@ import 'package:flutter/material.dart'; import 'package:flutter/cupertino.dart'; import 'package:flutter_application_1/calculator.dart'; import 'package:flutter_application_1/enums.dart'; -import 'package:flutter_application_1/translations.dart'; import 'package:flutter_application_1/utils.dart'; import 'package:flutter_application_1/widgets/input_widget.dart'; import 'package:flutter_application_1/widgets/interval_widget.dart'; diff --git a/lib/translations.dart b/lib/translations.dart deleted file mode 100644 index 7bc432a..0000000 --- a/lib/translations.dart +++ /dev/null @@ -1,10 +0,0 @@ -import 'package:flutter_application_1/enums.dart'; - -String translateInterval(PayoutInterval interval) { - switch (interval) { - case PayoutInterval.yearly: - return 'jährlich'; - case PayoutInterval.monthly: - return 'monatlich'; - } -} \ No newline at end of file diff --git a/lib/utils.dart b/lib/utils.dart index 9828862..c80c195 100644 --- a/lib/utils.dart +++ b/lib/utils.dart @@ -1,4 +1,5 @@ import 'package:flutter/material.dart'; +import 'package:flutter_application_1/enums.dart'; // Rundet den Wert im Textfeld-Controller auf die nächste ganze Zahl void roundToInteger(TextEditingController controller) { @@ -19,4 +20,13 @@ void restoreDefaultValuesIfEmpty(TextEditingController controller) { if (controller.text.isEmpty) { controller.text = '0'; } +} + +String translateInterval(PayoutInterval interval) { + switch (interval) { + case PayoutInterval.yearly: + return 'jährlich'; + case PayoutInterval.monthly: + return 'monatlich'; + } } \ No newline at end of file diff --git a/lib/widgets/chart_widget.dart b/lib/widgets/chart_widget.dart index e5c4a96..97f28ce 100644 --- a/lib/widgets/chart_widget.dart +++ b/lib/widgets/chart_widget.dart @@ -1,4 +1,5 @@ import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; import 'package:syncfusion_flutter_charts/charts.dart'; // Widget zur Erstellung eines gestapelten Säulendiagramms @@ -79,3 +80,155 @@ class SalesData { SalesData(this.year, this.value); } + +// Widget, welches das Diagramm auf eine neue Seite auslagert +@override +Widget buildChartPage(BuildContext context, List investedMoneyList, List compoundInterestList) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Container( + padding: EdgeInsets.only(left: 10, right: 10, top: MediaQuery.of(context).padding.top + 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(CupertinoIcons.chevron_left, size: 15), // Zurück-Pfeil + onPressed: () { + Navigator.pop(context); + }, + ), + const Text( + 'Grafik', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(width: 40), // Platzhalter für zentrierte Ausrichtung + ], + ), + ), + ), + SliverToBoxAdapter( // Diagramm Anzeigen + child: Padding( + padding: const EdgeInsets.all(10.0), + child: StackedColumnChart( + lowerValues: investedMoneyList, + upperValues: compoundInterestList, + ), + ), + ), + SliverToBoxAdapter( // Tabelle mit allen Werten anzeigen + child: Padding( + padding: const EdgeInsets.all(10.0), + child: Container( + decoration: BoxDecoration( + color: CupertinoColors.black, + borderRadius: BorderRadius.circular(5), + ), + clipBehavior: Clip.antiAlias, + child: Table( + columnWidths: const { + 0: FixedColumnWidth(60), // Spaltenbreite für die erste Spalte verringern + 1: FlexColumnWidth(), + 2: FlexColumnWidth(), + 3: FlexColumnWidth(), + }, + border: TableBorder.symmetric( + inside: const BorderSide(color: CupertinoColors.white, width: 1), + ), + children: [ + const TableRow( + decoration: BoxDecoration(color: CupertinoColors.darkBackgroundGray), + children: [ + Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'Jahr', + style: TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'Einzahlungen', + style: TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'Zinsen', + style: TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: EdgeInsets.all(8.0), + child: Text( + 'Endkapital', + style: TextStyle( + color: CupertinoColors.white, + fontWeight: FontWeight.bold, + ), + textAlign: TextAlign.center, + ), + ), + ], + ), + for (int i = 0; i < investedMoneyList.length; i++) + TableRow( + children: [ + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '${i + 1}', + style: const TextStyle(color: CupertinoColors.white), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '€${investedMoneyList[i]}', + style: const TextStyle(color: CupertinoColors.white), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '€${compoundInterestList[i]}', + style: const TextStyle(color: CupertinoColors.white), + textAlign: TextAlign.center, + ), + ), + Padding( + padding: const EdgeInsets.all(8.0), + child: Text( + '€${compoundInterestList[i] + investedMoneyList[i]}', + style: const TextStyle(color: CupertinoColors.white), + textAlign: TextAlign.center, + ), + ), + ], + ), + ], + ), + ), + ), + ), + ], + ), + ); +} \ No newline at end of file diff --git a/lib/widgets/interval_widget.dart b/lib/widgets/interval_widget.dart index 2356b53..4886e1a 100644 --- a/lib/widgets/interval_widget.dart +++ b/lib/widgets/interval_widget.dart @@ -1,7 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_application_1/enums.dart'; -import 'package:flutter_application_1/translations.dart'; +import 'package:flutter_application_1/utils.dart'; class IntervalWidget extends StatefulWidget { final String selectedInterval; diff --git a/lib/widgets/milestone_timeline_widget.dart b/lib/widgets/milestone_timeline_widget.dart index 3eb3233..d365a19 100644 --- a/lib/widgets/milestone_timeline_widget.dart +++ b/lib/widgets/milestone_timeline_widget.dart @@ -1,6 +1,7 @@ -// Erstellt eine Zeitleiste für Meilensteine import 'package:flutter/cupertino.dart'; +import 'package:flutter/material.dart'; +// Erstellt eine Zeitleiste für Meilensteine Widget buildMilestoneTimeline(List> milestones, double totalInterest) { return SizedBox( child: Center( @@ -50,3 +51,41 @@ Widget buildMilestoneTimeline(List> milestones, double tota ), ); } + +// Widget, welches den Meilenstein-Zeitstrahl auf eine neue Seite auslagert +@override +Widget buildMilestonePage(BuildContext context, String compoundInterest, String investedMoney, List> milestoneList) { + return Scaffold( + body: CustomScrollView( + slivers: [ + SliverToBoxAdapter( + child: Container( + padding: EdgeInsets.only(left: 10, right: 10, top: MediaQuery.of(context).padding.top + 10), + child: Row( + mainAxisAlignment: MainAxisAlignment.spaceBetween, + children: [ + IconButton( + icon: const Icon(CupertinoIcons.chevron_left, size: 15), // Zurück-Pfeil + onPressed: () { + Navigator.pop(context); + }, + ), + const Text( + 'Grafik', + style: TextStyle(fontWeight: FontWeight.bold), + ), + const SizedBox(width: 40), // Platzhalter für zentrierte Ausrichtung + ], + ), + ), + ), + SliverToBoxAdapter( // Meilenstein-Zeitstrahl anzeigen + child: Padding( + padding: const EdgeInsets.all(10.0), + child: buildMilestoneTimeline(milestoneList, double.parse(compoundInterest) - double.parse(investedMoney)) + ), + ), + ], + ), + ); +} diff --git a/lib/widgets/result_widget.dart b/lib/widgets/result_widget.dart index e04a2d3..8243bde 100644 --- a/lib/widgets/result_widget.dart +++ b/lib/widgets/result_widget.dart @@ -1,8 +1,7 @@ import 'package:flutter/cupertino.dart'; import 'package:flutter/material.dart'; import 'package:flutter_application_1/enums.dart'; -import 'package:flutter_application_1/translations.dart'; -import 'package:flutter_application_1/widgets/second_page_widget.dart'; +import 'package:flutter_application_1/utils.dart'; import 'package:flutter_application_1/widgets/custom_image_button_widget.dart'; import 'package:flutter_application_1/widgets/chart_widget.dart'; import 'package:flutter_application_1/widgets/milestone_timeline_widget.dart'; @@ -20,18 +19,20 @@ Widget buildResultWidget(BuildContext context, String compoundInterest, String i return Column( children: [ + _buildResultRow('Endkapital:', '$compoundInterest€'), + _buildResultRow('Einzahlungen:', '$investedMoney€'), + _buildResultRow('Erhaltene Zinsen:', '${double.parse(compoundInterest) + double.parse(investedMoney)}€'), + const SizedBox(height: 10), + Text( + 'Wenn du über einen Zeitraum von $time Jahren ${translateInterval(payoutInterval)} $monthlySavingsRate€ mit einem Zinssatz von $interestRate% investierst, erreichst du am Ende ein Endkapital von $compoundInterest€. Dieses setzt sich aus Einzahlungen von $investedMoney€ und Zinsen bzw. Kapitalerträgen in Höhe von ${double.parse(compoundInterest) - double.parse(investedMoney)}€ zusammen.' + ), + const SizedBox(height: 20), CustomImageButton( onPressed: () { Navigator.push( context, MaterialPageRoute( - builder: (context) => SecondPage( - title: 'Grafik', - widgetToShow: StackedColumnChart( - lowerValues: investedMoneyList, - upperValues: compoundInterestList, - ) - ), + builder: (context) => buildChartPage(context, investedMoneyList, compoundInterestList), ), ); }, @@ -47,10 +48,7 @@ Widget buildResultWidget(BuildContext context, String compoundInterest, String i Navigator.push( context, MaterialPageRoute( - builder: (context) => SecondPage( - title: 'Meilensteine', - widgetToShow: buildMilestoneTimeline(milestoneList, double.parse(compoundInterest) - double.parse(investedMoney)) - ), + builder: (context) => buildMilestonePage(context, compoundInterest, investedMoney, milestoneList), ), ); }, @@ -60,14 +58,6 @@ Widget buildResultWidget(BuildContext context, String compoundInterest, String i style: TextStyle(color: CupertinoColors.white, fontSize: 20), ), ), - const SizedBox(height: 20), - _buildResultRow('Endkapital:', '$compoundInterest€'), - _buildResultRow('Einzahlungen:', '$investedMoney€'), - _buildResultRow('Erhaltene Zinsen:', '${double.parse(compoundInterest) - double.parse(investedMoney)}€'), - const SizedBox(height: 10), - Text( - 'Wenn du über einen Zeitraum von $time Jahren ${translateInterval(payoutInterval)} $monthlySavingsRate€ mit einem Zinssatz von $interestRate% investierst, erreichst du am Ende ein Endkapital von $compoundInterest€. Dieses setzt sich aus Einzahlungen von $investedMoney€ und Zinsen bzw. Kapitalerträgen in Höhe von ${double.parse(compoundInterest) - double.parse(investedMoney)}€ zusammen.' - ), ], ); diff --git a/lib/widgets/second_page_widget.dart b/lib/widgets/second_page_widget.dart deleted file mode 100644 index 95e3e2d..0000000 --- a/lib/widgets/second_page_widget.dart +++ /dev/null @@ -1,50 +0,0 @@ -import 'package:flutter/cupertino.dart'; -import 'package:flutter/material.dart'; - -class SecondPage extends StatelessWidget { - final String title; - final Widget widgetToShow; - - const SecondPage({ - super.key, - required this.title, - required this.widgetToShow - }); - - @override - Widget build(BuildContext context) { - return Scaffold( - body: CustomScrollView( - slivers: [ - SliverToBoxAdapter( - child: Container( - padding: EdgeInsets.only(left: 10, right: 10, top: MediaQuery.of(context).padding.top + 10), - child: Row( - mainAxisAlignment: MainAxisAlignment.spaceBetween, - children: [ - IconButton( - icon: const Icon(CupertinoIcons.chevron_left, size: 15), // Zurück-Pfeil - onPressed: () { - Navigator.pop(context); - }, - ), - Text( - title, - style: const TextStyle(fontWeight: FontWeight.bold), - ), - const SizedBox(width: 40), // Platzhalter für zentrierte Ausrichtung - ], - ), - ), - ), - SliverToBoxAdapter( - child: Padding( - padding: const EdgeInsets.all(10.0), - child: widgetToShow, // Anzeigen des mitgegebenen Widgets - ), - ), - ], - ), - ); - } -} diff --git a/test/unit_test.dart b/test/unit_test.dart new file mode 100644 index 0000000..1c2baae --- /dev/null +++ b/test/unit_test.dart @@ -0,0 +1,44 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_application_1/enums.dart'; +import 'package:flutter_application_1/calculator.dart'; + +void main() { + group('Calculation Tests', () { + test('Test calculateInvestedMoney function', () { + // Testen mit verschiedenen Eingabewerten + expect(calculateInvestedMoney(1000, 100, 5, []), equals(7000.0)); + expect(calculateInvestedMoney(2000, 50, 10, []), equals(8000.0)); + }); + test('Test calculateCompoundInterest function', () { + // Testen mit jährlicher Auszahlung + List investedMoneyListYearly = []; + List compoundInterestListYearly = []; + calculateInvestedMoney(1000, 100, 10, investedMoneyListYearly); + expect(calculateCompoundInterest(1000, 100, 5, 10, PayoutInterval.yearly, investedMoneyListYearly, compoundInterestListYearly), equals(16722.0)); + + // Testen mit monatlicher Auszahlung + List investedMoneyListMonthly = []; + List compoundInterestListMonthly = []; + calculateInvestedMoney(2000, 50, 10, investedMoneyListMonthly); + expect(calculateCompoundInterest(2000, 50, 5, 10, PayoutInterval.monthly, investedMoneyListMonthly, compoundInterestListMonthly), equals(11058.0)); + }); + }); + group('Edge Case Tests', () { + test('Test calculateInvestedMoney function with extremely high input values', () { + // Testen mit extrem hohen Eingabewerten + expect(calculateInvestedMoney(1e15, 1e12, 100, []), equals(2.2e15)); + }); + test('Test calculateInvestedMoney function with extremely low input values', () { + // Testen mit extrem niedrigen Eingabewerten + expect(calculateInvestedMoney(0, 0, 0, []), equals(0.0)); + expect(calculateInvestedMoney(0.1, 0.01, 0.001, []), equals(0.0)); + }); + test('Test calculateCompoundInterest function with extremely high interest rate', () { + // Testen mit extrem hohen Zinssatz + List investedMoneyListHighInterest = []; + List compoundInterestListHighInterest = []; + calculateInvestedMoney(1000, 100, 10, investedMoneyListHighInterest); + expect(calculateCompoundInterest(1000, 100, 1000, 10, PayoutInterval.yearly, investedMoneyListHighInterest, compoundInterestListHighInterest), equals(29049915553000.0)); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart new file mode 100644 index 0000000..dbb2a48 --- /dev/null +++ b/test/widget_test.dart @@ -0,0 +1 @@ +import 'package:flutter_test/flutter_test.dart'; \ No newline at end of file