diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 4bb6e7e..52497a7 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -2,6 +2,7 @@ image: cirrusci/flutter:3.7.5 stages: - analyze + - test before_script: - flutter pub get @@ -13,3 +14,10 @@ analyze: - flutter analyze only: - merge_requests + +test: + stage: test + script: + - flutter test + only: + - merge_requests diff --git a/lib/globals.dart b/lib/globals.dart index 1381241..9e7c817 100644 --- a/lib/globals.dart +++ b/lib/globals.dart @@ -1,10 +1,8 @@ library app.globals; -import 'package:smoke_cess_app/mock/db_mock.dart'; import 'package:smoke_cess_app/services/database_service.dart'; -DatabaseService databaseService = DatabaseMock(); -// DatabaseService databaseService = DatabaseService.instance; +DatabaseService databaseService = DatabaseService.instance; // set this to read settings from local json file instead of scanning a qr code bool useLocalConfig = true; diff --git a/lib/providers/input_provider.dart b/lib/providers/input_provider.dart index b8aed63..01ab835 100644 --- a/lib/providers/input_provider.dart +++ b/lib/providers/input_provider.dart @@ -22,7 +22,9 @@ class InputProvider extends ChangeNotifier { TextEditingController get textController => _textController; set sliderValue(double newValue) { - _sliderValue = newValue; + if (_sliderValue > 0 && _sliderValue < 100) { + _sliderValue = newValue; + } notifyListeners(); } diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 121ff0d..49c92b8 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -21,7 +21,7 @@ class SettingsProvider extends ChangeNotifier { initSettings(); } - void initSettings() async { + Future initSettings() async { _settings = await loadSettings(); _initialized = _settings != null ? true : false; notifyListeners(); diff --git a/lib/providers/tasks_provider.dart b/lib/providers/tasks_provider.dart index 2fed9b8..17b2aa5 100644 --- a/lib/providers/tasks_provider.dart +++ b/lib/providers/tasks_provider.dart @@ -31,7 +31,7 @@ class TasksProvider extends ChangeNotifier { notifyListeners(); } - void initHistories() async { + Future initHistories() async { moodHistory = await globals.databaseService.getMoodRecords(); sleepHistory = await globals.databaseService.getSleepRecords(); workoutHistory = await globals.databaseService.getWorkoutRecords(); diff --git a/lib/providers/workout_provider.dart b/lib/providers/workout_provider.dart index ebcaf9c..0db0c82 100644 --- a/lib/providers/workout_provider.dart +++ b/lib/providers/workout_provider.dart @@ -100,25 +100,25 @@ class WorkoutProvider extends ChangeNotifier { Map> _workoutPhaseSettings = { WorkoutPhases.warmUp: { 'title': 'Warm Up', - 'duration': const Duration(seconds: 5), + 'duration': const Duration(minutes: 5), 'source': AssetSource('warmUp.mp3'), 'color': Colors.green }, WorkoutPhases.highIntensity: { 'title': 'High Intensity', - 'duration': const Duration(seconds: 4), + 'duration': const Duration(minutes: 4), 'source': AssetSource('workout.mp3'), 'color': Colors.red }, WorkoutPhases.lowIntensity: { 'title': 'Low Intensity', - 'duration': const Duration(seconds: 3), + 'duration': const Duration(minutes: 3), 'source': AssetSource('workout.mp3'), 'color': Colors.orange }, WorkoutPhases.coolDown: { 'title': 'Cool Down', - 'duration': const Duration(seconds: 5), + 'duration': const Duration(minutes: 5), 'source': AssetSource('cool_down.mp3'), 'color': Colors.blue } diff --git a/lib/services/date_service.dart b/lib/services/date_service.dart index 79bdd37..e4a9617 100644 --- a/lib/services/date_service.dart +++ b/lib/services/date_service.dart @@ -3,7 +3,7 @@ import 'package:timezone/timezone.dart'; import 'pages_service.dart'; -const int trainingTime = 40; +const int trainingTime = 6 * 7; const weekDays = { "Montag": 1, diff --git a/lib/utils/timer_util.dart b/lib/utils/timer_util.dart index 554173a..9fba645 100644 --- a/lib/utils/timer_util.dart +++ b/lib/utils/timer_util.dart @@ -1,6 +1,9 @@ import 'package:flutter/material.dart'; String formatTime(int seconds) { + if (seconds < 0) { + return '00:00'; + } Duration duration = Duration(seconds: seconds); String formattedTime = ''; String twoDigits(int n) => n.toString().padLeft(2, "0"); diff --git a/lib/widgets/view_form/mood_form.dart b/lib/widgets/view_form/mood_form.dart index 588d542..d287767 100644 --- a/lib/widgets/view_form/mood_form.dart +++ b/lib/widgets/view_form/mood_form.dart @@ -13,8 +13,8 @@ class MoodForm extends StatelessWidget { @override Widget build(BuildContext context) { - var inputModel = context.watch(); - var tasksModel = context.watch(); + InputProvider inputModel = context.watch(); + TasksProvider tasksModel = context.watch(); return ListView( padding: const EdgeInsets.fromLTRB(10, 10, 10, 10), children: [ diff --git a/lib/widgets/view_form/sleep_view.dart b/lib/widgets/view_form/sleep_view.dart index e53837c..add4e8f 100644 --- a/lib/widgets/view_form/sleep_view.dart +++ b/lib/widgets/view_form/sleep_view.dart @@ -27,7 +27,7 @@ class SleepView extends StatelessWidget { history: tasksModel.sleepHistory, dateSelector: (Sleep sleep) => sleep.date, entryDataSelector: (Sleep sleep) => - '${sleep.sleepDuration.hour}:${sleep.sleepDuration.minute}', + '${sleep.sleepDuration.hour}:${sleep.sleepDuration.minute.toString().padLeft(2, "0")}', entryCommentSelector: (Sleep sleep) => 'Kommentar: ${sleep.comment}', icon: Icons.bedtime_outlined, ) diff --git a/test/integration_tests/mood_form_test.dart b/test/integration_tests/mood_form_test.dart new file mode 100644 index 0000000..c723e0a --- /dev/null +++ b/test/integration_tests/mood_form_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/providers/page_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/widgets/view_form/mood_form.dart'; +import 'package:smoke_cess_app/globals.dart' as globals; +import '../mock/db_mock.dart'; + +void main() { + globals.databaseService = DatabaseMock(); + testWidgets('Mood Form saves correctly', (WidgetTester tester) async { + const String testText = 'Its a test'; + // Create an instance of the Providers and add it to the widget tree + final inputProvider = InputProvider(); + final tasksProvider = TasksProvider(null); + final pageProvider = PageProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ChangeNotifierProvider.value(value: tasksProvider), + ChangeNotifierProvider.value(value: pageProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MoodForm(), + ), + ), + ), + ); + + await tester.enterText(find.byType(TextField), testText); + await tester.tap(find.byType(ElevatedButton)); + await tester.pump(); + final result = await globals.databaseService.getMoodRecords(); + expect(result.last.comment, testText); + expect(result.last.moodValue, 50); + }); +} diff --git a/test/integration_tests/mood_view_test.dart b/test/integration_tests/mood_view_test.dart new file mode 100644 index 0000000..00bb61b --- /dev/null +++ b/test/integration_tests/mood_view_test.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:smoke_cess_app/models/mood.dart'; +import 'package:smoke_cess_app/providers/settings_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/globals.dart' as globals; +import 'package:smoke_cess_app/widgets/view_form/mood_view.dart'; +import '../mock/db_mock.dart'; +import '../mock/settings_mock.dart'; + +void main() { + globals.databaseService = DatabaseMock(); + TestWidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues(mockSettings); + initializeDateFormatting('de'); + testWidgets('Mood View displays correctly', (WidgetTester tester) async { + const String testText = 'Its a test'; + const int testValue = 30; + // Create an instance of the Providers and add it to the widget tree + final tasksProvider = TasksProvider(null); + final settingsProvider = SettingsProvider(); + + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: tasksProvider), + ChangeNotifierProvider.value(value: settingsProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MoodView(), + ), + ), + ), + ); + await settingsProvider.initSettings(); + await globals.databaseService + .addMood(Mood(testValue, testText, DateTime.now())); + await tasksProvider.initHistories(); + await tester.pump(); + expect(find.text('Stimmung: $testValue'), findsOneWidget); + await tester.tap(find.byIcon(Icons.expand_more)); + await tester.pump(); + expect(find.text('Kommentar: $testText'), findsOneWidget); + }); +} diff --git a/test/integration_tests/relapse_form_test.dart b/test/integration_tests/relapse_form_test.dart new file mode 100644 index 0000000..cdf7321 --- /dev/null +++ b/test/integration_tests/relapse_form_test.dart @@ -0,0 +1,57 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/providers/page_provider.dart'; +import 'package:smoke_cess_app/providers/settings_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/globals.dart' as globals; +import 'package:smoke_cess_app/widgets/drop_down.dart'; +import 'package:smoke_cess_app/widgets/view_form/relapse_form.dart'; +import '../mock/db_mock.dart'; +import '../mock/settings_mock.dart'; + +void main() { + globals.databaseService = DatabaseMock(); + TestWidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues(mockSettings); + testWidgets('Relapse Form saves correctly', (WidgetTester tester) async { + const String testText = 'Its a test'; + // Create an instance of the Providers and add it to the widget tree + final inputProvider = InputProvider(); + final tasksProvider = TasksProvider(null); + final pageProvider = PageProvider(); + final settingsProvider = SettingsProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ChangeNotifierProvider.value(value: tasksProvider), + ChangeNotifierProvider.value(value: pageProvider), + ChangeNotifierProvider.value(value: settingsProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: RelapseForm(), + ), + ), + ), + ); + + await settingsProvider.initSettings(); + + await tester.enterText(find.byType(TextField), testText); + await tester.tap(find.byType(DropDown)); + await tester.pump(); + await tester + .tap(find.text(settingsProvider.settings!.relapseCategories![1]).last); + await tester.pump(); + await tester.tap(find.byType(ElevatedButton).last); + await tester.pump(); + final result = await globals.databaseService.getRelapseRecords(); + expect(result.last.comment, testText); + expect( + result.last.category, settingsProvider.settings!.relapseCategories![1]); + }); +} diff --git a/test/integration_tests/relapse_view_test.dart b/test/integration_tests/relapse_view_test.dart new file mode 100644 index 0000000..96f3ee9 --- /dev/null +++ b/test/integration_tests/relapse_view_test.dart @@ -0,0 +1,52 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:smoke_cess_app/models/relapse.dart'; +import 'package:smoke_cess_app/providers/settings_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/globals.dart' as globals; +import 'package:smoke_cess_app/widgets/view_form/relapse_view.dart'; +import '../mock/db_mock.dart'; +import '../mock/settings_mock.dart'; + +void main() { + globals.databaseService = DatabaseMock(); + TestWidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues(mockSettings); + initializeDateFormatting('de'); + testWidgets('Relapse View displays correctly', (WidgetTester tester) async { + const String testText = 'Its a test'; + // Create an instance of the Providers and add it to the widget tree + final tasksProvider = TasksProvider(null); + final settingsProvider = SettingsProvider(); + + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: tasksProvider), + ChangeNotifierProvider.value(value: settingsProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: RelapseView(), + ), + ), + ), + ); + await settingsProvider.initSettings(); + await globals.databaseService.addRelapse(Relapse( + settingsProvider.settings!.relapseCategories![0], + testText, + DateTime.now(), + )); + await tasksProvider.initHistories(); + await tester.pump(); + expect(find.text(settingsProvider.settings!.relapseCategories![0]), + findsOneWidget); + await tester.tap(find.byIcon(Icons.expand_more)); + await tester.pump(); + expect(find.text('Kommentar: $testText'), findsOneWidget); + }); +} diff --git a/test/integration_tests/sleep_form_test.dart b/test/integration_tests/sleep_form_test.dart new file mode 100644 index 0000000..b833f08 --- /dev/null +++ b/test/integration_tests/sleep_form_test.dart @@ -0,0 +1,42 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/providers/page_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/globals.dart' as globals; +import 'package:smoke_cess_app/widgets/view_form/sleep_form.dart'; +import '../mock/db_mock.dart'; + +void main() { + globals.databaseService = DatabaseMock(); + testWidgets('Sleep Form saves correctly', (WidgetTester tester) async { + const String testText = 'Its a test'; + // Create an instance of the Providers and add it to the widget tree + final inputProvider = InputProvider(); + final tasksProvider = TasksProvider(null); + final pageProvider = PageProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ChangeNotifierProvider.value(value: tasksProvider), + ChangeNotifierProvider.value(value: pageProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: SleepForm(), + ), + ), + ), + ); + + await tester.enterText(find.byType(TextField), testText); + await tester.tap(find.byType(ElevatedButton).last); + await tester.pump(); + final result = await globals.databaseService.getSleepRecords(); + expect(result.last.comment, testText); + expect(result.last.sleepQualitiyValue, 50); + expect(result.last.sleepDuration, const TimeOfDay(hour: 10, minute: 0)); + }); +} diff --git a/test/integration_tests/sleep_view_test.dart b/test/integration_tests/sleep_view_test.dart new file mode 100644 index 0000000..b8f2e1e --- /dev/null +++ b/test/integration_tests/sleep_view_test.dart @@ -0,0 +1,53 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:provider/provider.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:smoke_cess_app/models/sleep.dart'; +import 'package:smoke_cess_app/providers/settings_provider.dart'; +import 'package:smoke_cess_app/providers/tasks_provider.dart'; +import 'package:smoke_cess_app/globals.dart' as globals; +import 'package:smoke_cess_app/widgets/view_form/sleep_view.dart'; +import '../mock/db_mock.dart'; +import '../mock/settings_mock.dart'; + +void main() { + globals.databaseService = DatabaseMock(); + TestWidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues(mockSettings); + initializeDateFormatting('de'); + testWidgets('Sleep View displays correctly', (WidgetTester tester) async { + const String testText = 'Its a test'; + const int testValue = 30; + // Create an instance of the Providers and add it to the widget tree + final tasksProvider = TasksProvider(null); + final settingsProvider = SettingsProvider(); + + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: tasksProvider), + ChangeNotifierProvider.value(value: settingsProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: SleepView(), + ), + ), + ), + ); + await settingsProvider.initSettings(); + await globals.databaseService.addSleep(Sleep( + testValue, + testText, + DateTime.now(), + const TimeOfDay(hour: 22, minute: 0), + const TimeOfDay(hour: 8, minute: 0))); + await tasksProvider.initHistories(); + await tester.pump(); + expect(find.text('10:00'), findsOneWidget); + await tester.tap(find.byIcon(Icons.expand_more)); + await tester.pump(); + expect(find.text('Kommentar: $testText'), findsOneWidget); + }); +} diff --git a/test/mock/audio_provider_mock.dart b/test/mock/audio_provider_mock.dart new file mode 100644 index 0000000..0a723b4 --- /dev/null +++ b/test/mock/audio_provider_mock.dart @@ -0,0 +1,22 @@ +import 'package:audioplayers/audioplayers.dart'; +import 'package:smoke_cess_app/providers/audio_provider.dart'; + +class AudioProviderMock extends AudioProvider { + @override + bool get isMuted => true; + + @override + void stop() {} + + @override + void playFinishSound() {} + + @override + void mutePlayer() {} + + @override + void unMutePlayer() {} + + @override + void playSourceAfterBeep(AssetSource source) {} +} diff --git a/lib/mock/db_mock.dart b/test/mock/db_mock.dart similarity index 100% rename from lib/mock/db_mock.dart rename to test/mock/db_mock.dart diff --git a/test/mock/settings_mock.dart b/test/mock/settings_mock.dart new file mode 100644 index 0000000..1f0be5d --- /dev/null +++ b/test/mock/settings_mock.dart @@ -0,0 +1,22 @@ +import 'package:shared_preferences/shared_preferences.dart'; + +Map mockSettings = { + "group": 3, + "HITT_time": 35, + "chess_time": {"hours": 8, "minutes": 30}, + "relapse_categories": ["App stresst mich", "langeweile", "lunge braucht es"], + "mood_query": { + "days": ["Montag", "Donnerstag"], + "hours": 10, + "minutes": 30 + }, + "sleep_query": { + "days": ["Dienstag", "Samstag"], + "hours": 15, + "minutes": 42 + }, + "startedAt": DateTime.now().toIso8601String(), +}; + +void mockSharedPreferences() => + SharedPreferences.setMockInitialValues(mockSettings); diff --git a/test/unit_tests/date_service_test.dart b/test/unit_tests/date_service_test.dart new file mode 100644 index 0000000..afb3ee5 --- /dev/null +++ b/test/unit_tests/date_service_test.dart @@ -0,0 +1,124 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:smoke_cess_app/services/date_service.dart'; +import 'package:smoke_cess_app/services/pages_service.dart'; +import 'package:timezone/data/latest.dart' as tz; +import 'package:timezone/timezone.dart'; +import '../mock/settings_mock.dart'; + +void main() async { + tz.initializeTimeZones(); + WidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues(mockSettings); + group('Helpers', () { + group('isSameDay', () { + test('returns true when dates are the same day', () { + DateTime dateA = DateTime.now(); + DateTime dateB = DateTime(dateA.year, dateA.month, dateA.day, 10, 0); + expect(isSameDay(dateA, dateB), true); + }); + + test('returns false when dates are different days', () { + DateTime dateA = DateTime.now(); + DateTime dateB = + DateTime(dateA.year, dateA.month, dateA.day + 1, 10, 0); + expect(isSameDay(dateA, dateB), false); + }); + + test('returns false when one date is null', () { + DateTime? dateA = DateTime.now(); + DateTime? dateB; + expect(isSameDay(dateA, dateB), false); + }); + }); + + group('createTZDateTimes', () { + test('returns empty list when selectedDays is null', () { + List tzDateTimes = createTZDateTimes(null, 10, 0); + expect(tzDateTimes, []); + }); + + test('returns empty list when selectedHours is null', () { + List tzDateTimes = createTZDateTimes(['Montag'], null, 0); + expect(tzDateTimes, []); + }); + + test('returns empty list when selectedMinutes is null', () { + List tzDateTimes = createTZDateTimes(['Montag'], 10, null); + expect(tzDateTimes, []); + }); + + test('returns empty list when no valid dates are found', () { + List tzDateTimes = createTZDateTimes(['Mong'], 10, 0); + expect(tzDateTimes, []); + }); + + test('returns list of valid dates', () { + List tzDateTimes = createTZDateTimes(['Montag'], 10, 0); + expect(tzDateTimes.length, greaterThan(0)); + }); + + test('returns all the same days as today in next 6 weeks at 23:59', () { + List selectedDays = [ + weekDays.keys.elementAt(DateTime.now().weekday - 1) + ]; + List expected = []; + DateTime now = DateTime.now(); + final Duration offset = now.timeZoneOffset; + final DateTime date = + DateTime(now.year, now.month, now.day, 23, 59, 0, 0, 0); + for (int i = 0; i <= trainingTime; i = i + 7) { + expected.add( + TZDateTime.local(date.year, date.month, date.day, 23, 59, 0, 0, 0) + .add(Duration(days: i)) + // subtract offset since TZDateTime uses the UTC Timezone + .subtract(offset)); + } + List result = createTZDateTimes(selectedDays, 23, 59); + expect(result, expected); + }); + }); + + group('getTimeTill', () { + test('returns time till next mood', () async { + Duration duration = await getTimeTill(Pages.mood); + expect(duration, isNotNull); + }); + + test('returns time till next sleep', () async { + Duration duration = await getTimeTill(Pages.sleep); + expect(duration, isNotNull); + }); + test('returns time till next workout', () async { + Duration duration = await getTimeTill(Pages.timer); + expect(duration, isNotNull); + }); + }); + }); + + group('Helper functions', () { + test('isSameDay returns true if two dates are the same day', () { + final date1 = DateTime(2022, 3, 6); + final date2 = DateTime(2022, 3, 6, 15, 30); + final result = isSameDay(date1, date2); + expect(result, true); + }); + + test('isSameDay returns false if two dates are not the same day', () { + final date1 = DateTime(2022, 3, 6); + final date2 = DateTime(2022, 3, 7); + final result = isSameDay(date1, date2); + expect(result, false); + }); + + test('createTZDateTimes returns a list of TZDateTime objects', () async { + final selectedDays = ['Montag', 'Dienstag']; + const selectedHours = 12; + const selectedMinutes = 30; + final result = + createTZDateTimes(selectedDays, selectedHours, selectedMinutes); + expect(result, isA>()); + }); + }); +} diff --git a/test/unit_tests/input_provider_test.dart b/test/unit_tests/input_provider_test.dart new file mode 100644 index 0000000..36fcd3e --- /dev/null +++ b/test/unit_tests/input_provider_test.dart @@ -0,0 +1,102 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/globals.dart' as globals; + +import '../mock/db_mock.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + globals.databaseService = DatabaseMock(); + group('InputProvider', () { + test('Initial values are correct', () { + late final inputProvider = InputProvider(); + + expect(inputProvider.sliderValue, equals(50)); + expect(inputProvider.textController.text, equals('')); + expect(inputProvider.relapseCategory, equals('')); + expect(inputProvider.getTimeEntry(SleepTimes.wokeUpAt), + equals(const TimeOfDay(hour: 8, minute: 0))); + expect(inputProvider.getTimeEntry(SleepTimes.sleptAt), + equals(const TimeOfDay(hour: 22, minute: 0))); + }); + + test('Setters work right way', () { + late final inputProvider = InputProvider(); + + inputProvider.sliderValue = 75; + expect(inputProvider.sliderValue, equals(75)); + + inputProvider.textController.text = 'Test'; + expect(inputProvider.textController.text, equals('Test')); + + inputProvider.relapseCategory = 'Test'; + expect(inputProvider.relapseCategory, equals('Test')); + + inputProvider.setTime( + SleepTimes.wokeUpAt, const TimeOfDay(hour: 7, minute: 0)); + expect(inputProvider.getTimeEntry(SleepTimes.wokeUpAt), + equals(const TimeOfDay(hour: 7, minute: 0))); + + inputProvider.setTime( + SleepTimes.sleptAt, const TimeOfDay(hour: 23, minute: 0)); + expect(inputProvider.getTimeEntry(SleepTimes.sleptAt), + equals(const TimeOfDay(hour: 23, minute: 0))); + }); + test('Reset Fields should reset all fields correctly', () async { + final inputProvider = InputProvider(); + + inputProvider.sliderValue = 44; + inputProvider.textController.text = 'Test'; + inputProvider.setTime( + SleepTimes.wokeUpAt, const TimeOfDay(hour: 7, minute: 0)); + inputProvider.setTime( + SleepTimes.sleptAt, const TimeOfDay(hour: 23, minute: 0)); + + final result = + await inputProvider.saveMood(); // calls private function ResetFields + + expect(result, equals(1)); + expect(inputProvider.sliderValue, equals(50)); + expect(inputProvider.textController.text, equals('')); + expect(inputProvider.getTimeEntry(SleepTimes.wokeUpAt), + equals(const TimeOfDay(hour: 8, minute: 0))); + expect(inputProvider.getTimeEntry(SleepTimes.sleptAt), + equals(const TimeOfDay(hour: 22, minute: 0))); + }); + test('Save Mood ', () async { + late final inputProvider = InputProvider(); + + final result = await inputProvider.saveMood(); + + expect(result, equals(1)); + expect(inputProvider.sliderValue, equals(50)); + expect(inputProvider.textController.text, equals('')); + }); + + test('Save Relapse', () async { + late final inputProvider = InputProvider(); + + final result = await inputProvider.saveRelapse(); + + expect(result, equals(1)); + expect(inputProvider.relapseCategory, equals('')); + expect(inputProvider.textController.text, equals('')); + }); + + test('Save Sleep', () async { + late final inputProvider = InputProvider(); + + final result = await inputProvider.saveSleep( + SleepTimes.wokeUpAt, SleepTimes.sleptAt); + + expect(result, equals(1)); + expect(inputProvider.sliderValue, equals(50)); + expect(inputProvider.textController.text, equals('')); + expect(inputProvider.getTimeEntry(SleepTimes.wokeUpAt), + equals(const TimeOfDay(hour: 8, minute: 0))); + expect(inputProvider.getTimeEntry(SleepTimes.sleptAt), + equals(const TimeOfDay(hour: 22, minute: 0))); + }); + }); +} diff --git a/test/unit_tests/settings_provider_test.dart b/test/unit_tests/settings_provider_test.dart new file mode 100644 index 0000000..61e0c65 --- /dev/null +++ b/test/unit_tests/settings_provider_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:shared_preferences/shared_preferences.dart'; +import 'package:smoke_cess_app/providers/settings_provider.dart'; +import '../mock/settings_mock.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + SharedPreferences.setMockInitialValues(mockSettings); + group('SettingsProvider', () { + test('initial state', () { + final provider = SettingsProvider(); + expect(provider.settings, isNull); + expect(provider.initialized, isFalse); + expect(provider.scanning, isFalse); + }); + + test('initialize mocksettings', () async { + final provider = SettingsProvider(); + await provider.initSettings(); + expect(provider.settings, isNotNull); + expect(provider.initialized, isTrue); + expect(provider.settings?.group, 3); + }); + + test('set scanning', () { + final provider = SettingsProvider(); + provider.scanning = true; + expect(provider.scanning, isTrue); + provider.scanning = false; + expect(provider.scanning, isFalse); + }); + }); +} diff --git a/test/unit_tests/timer_provider_test.dart b/test/unit_tests/timer_provider_test.dart new file mode 100644 index 0000000..46ed9f5 --- /dev/null +++ b/test/unit_tests/timer_provider_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:smoke_cess_app/providers/timer_provider.dart'; + +void main() { + test('Timer should start and set started to true', () { + final timerProvider = TimerProvider(); + timerProvider.startTimer(const Duration(seconds: 10)); + expect(timerProvider.started, true); + }); + + test('Elapsed time should increase and be less than or equal to the duration', + () { + final timerProvider = TimerProvider(); + timerProvider.startTimer(const Duration(seconds: 10)); + final initialElapsedSeconds = timerProvider.elapsedSeconds; + // Wait for the timer to tick at least once. + Future.delayed(const Duration(seconds: 2), () { + expect(timerProvider.elapsedSeconds, greaterThan(initialElapsedSeconds)); + expect(timerProvider.elapsedSeconds, lessThanOrEqualTo(10)); + }); + }); + + test('Timer should stop and set started to false', () { + final timerProvider = TimerProvider(); + timerProvider.startTimer(const Duration(seconds: 10)); + timerProvider.stopTimer(); + expect(timerProvider.started, false); + }); + + test('Elapsed seconds should be 0 when timer is not running', () { + final timerProvider = TimerProvider(); + expect(timerProvider.elapsedSeconds, 0); + }); +} diff --git a/test/unit_tests/timer_util_test.dart b/test/unit_tests/timer_util_test.dart new file mode 100644 index 0000000..50fe28f --- /dev/null +++ b/test/unit_tests/timer_util_test.dart @@ -0,0 +1,49 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:smoke_cess_app/utils/timer_util.dart'; + +void main() { + test('FormatTime: Seconds', () { + String result = formatTime(1); + expect(result, '00:01'); + }); + test('FormatTime: Minutes', () { + String result = formatTime(61); + expect(result, '01:01'); + }); + test('FormatTime: Hours', () { + String result = formatTime(3661); + expect(result, '01:01:01'); + }); + test('FormatTime: Day', () { + String result = formatTime(90061); + expect(result, '1 Tag, 01:01:01'); + }); + test('FormatTime: Days', () { + String result = formatTime(176461); + expect(result, '2 Tage, 01:01:01'); + }); + test('FormatTime: Negativ Value', () { + String result = formatTime(-1); + expect(result, '00:00'); + }); + + test('DurationBetween: 0', () { + TimeOfDay start = const TimeOfDay(hour: 12, minute: 0); + TimeOfDay end = const TimeOfDay(hour: 12, minute: 0); + TimeOfDay result = start.durationBetween(end); + expect(result, const TimeOfDay(hour: 0, minute: 0)); + }); + test('DurationBetween: 23h 59min', () { + TimeOfDay start = const TimeOfDay(hour: 0, minute: 0); + TimeOfDay end = const TimeOfDay(hour: 23, minute: 59); + TimeOfDay result = start.durationBetween(end); + expect(result, const TimeOfDay(hour: 23, minute: 59)); + }); + test('DurationBetween: 12h 34min', () { + TimeOfDay start = const TimeOfDay(hour: 2, minute: 12); + TimeOfDay end = const TimeOfDay(hour: 14, minute: 46); + TimeOfDay result = start.durationBetween(end); + expect(result, const TimeOfDay(hour: 12, minute: 34)); + }); +} diff --git a/test/unit_tests/workout_provider_test.dart b/test/unit_tests/workout_provider_test.dart new file mode 100644 index 0000000..1d5c84c --- /dev/null +++ b/test/unit_tests/workout_provider_test.dart @@ -0,0 +1,82 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter/material.dart'; +import 'package:smoke_cess_app/providers/audio_provider.dart'; +import 'package:smoke_cess_app/providers/timer_provider.dart'; +import 'package:smoke_cess_app/providers/workout_provider.dart'; +import 'package:smoke_cess_app/globals.dart' as globals; +import '../mock/audio_provider_mock.dart'; +import '../mock/db_mock.dart'; + +void main() { + WidgetsFlutterBinding.ensureInitialized(); + globals.databaseService = DatabaseMock(); + group('WorkoutProvider', () { + late WorkoutProvider workoutProvider; + late TimerProvider timerProvider; + late AudioProvider audioProvider; + + setUp(() { + timerProvider = TimerProvider(); + audioProvider = AudioProviderMock(); + workoutProvider = WorkoutProvider(timerProvider, audioProvider); + }); + + test('initial values', () { + expect(workoutProvider.isWorkoutStarted, false); + expect(workoutProvider.isWorkoutComplete, false); + expect(workoutProvider.motivationBefore, 50); + expect(workoutProvider.motivationAfter, 50); + expect(workoutProvider.currentPhase, WorkoutPhases.warmUp); + expect(workoutProvider.currentPhaseDuration, const Duration(minutes: 5)); + expect(workoutProvider.isPhaseComplete, false); + expect(workoutProvider.currentPhaseColor, Colors.green); + expect(workoutProvider.currentPhaseTitle, 'Warm Up'); + }); + + test('next phase', () { + workoutProvider.nextPhase(); + + expect(workoutProvider.currentPhase, WorkoutPhases.highIntensity); + expect(workoutProvider.currentPhaseDuration, const Duration(minutes: 4)); + expect(workoutProvider.isPhaseComplete, false); + expect(workoutProvider.currentPhaseColor, Colors.red); + expect(workoutProvider.currentPhaseTitle, 'High Intensity'); + }); + + test('start workout', () { + workoutProvider.startWorkout(); + + expect(workoutProvider.isWorkoutStarted, true); + expect(workoutProvider.isWorkoutComplete, false); + expect(workoutProvider.currentPhase, WorkoutPhases.warmUp); + expect(workoutProvider.currentPhaseDuration, const Duration(minutes: 5)); + expect(workoutProvider.isPhaseComplete, false); + }); + + test('stop workout', () { + workoutProvider.startWorkout(); + workoutProvider.stopWorkout(); + + expect(workoutProvider.isWorkoutStarted, false); + expect(workoutProvider.isWorkoutComplete, true); + }); + + test('interrupt workout', () { + workoutProvider.startWorkout(); + workoutProvider.interruptWorkout(); + + expect(workoutProvider.isWorkoutStarted, false); + expect(workoutProvider.isWorkoutComplete, false); + }); + + test('save workout', () async { + workoutProvider.motivationBefore = 70; + workoutProvider.motivationAfter = 80; + workoutProvider.saveWorkout(); + + final result = await globals.databaseService.getWorkoutRecords(); + + expect(result.length, 1); + }); + }); +} diff --git a/test/widget_test.dart b/test/widget_test.dart deleted file mode 100644 index ecf9aaf..0000000 --- a/test/widget_test.dart +++ /dev/null @@ -1,30 +0,0 @@ -// This is a basic Flutter widget test. -// -// To perform an interaction with a widget in your test, use the WidgetTester -// utility in the flutter_test package. For example, you can send tap and scroll -// gestures. You can also use WidgetTester to find child widgets in the widget -// tree, read text, and verify that the values of widget properties are correct. - -import 'package:flutter/material.dart'; -import 'package:flutter_test/flutter_test.dart'; - -import 'package:smoke_cess_app/main.dart'; - -void main() { - testWidgets('Counter increments smoke test', (WidgetTester tester) async { - // Build our app and trigger a frame. - await tester.pumpWidget(const MyApp()); - - // Verify that our counter starts at 0. - expect(find.text('0'), findsOneWidget); - expect(find.text('1'), findsNothing); - - // Tap the '+' icon and trigger a frame. - await tester.tap(find.byIcon(Icons.add)); - await tester.pump(); - - // Verify that our counter has incremented. - expect(find.text('0'), findsNothing); - expect(find.text('1'), findsOneWidget); - }); -} diff --git a/test/widget_tests/history_list_test.dart b/test/widget_tests/history_list_test.dart new file mode 100644 index 0000000..7354473 --- /dev/null +++ b/test/widget_tests/history_list_test.dart @@ -0,0 +1,130 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:intl/intl.dart'; +import 'package:smoke_cess_app/widgets/entry_detail_widget.dart'; +import 'package:smoke_cess_app/widgets/history_list_widget.dart'; + +void main() { + group('HistoryList', () { + initializeDateFormatting('de'); + final List history = ['1', '2', '3']; + dateSelector(String p0) => DateTime.now(); + entryDataSelector(String p0) => p0; + + testWidgets('should produce #(history.length) EntryDetail Widgets', + (widgetTester) async { + await widgetTester.pumpWidget(WrappedHistoryList( + history: history, + dateSelector: dateSelector, + entryDataSelector: entryDataSelector)); + final widgetFinder = find.byType(EntryDetail); + final dateFinder = + find.text(DateFormat.MMMd('de').format(DateTime.now())); + //HistoryList uses Icons.circle as default if there is no IconData provided + final iconFinder = find.byIcon(Icons.circle); + expect(widgetFinder, findsNWidgets(history.length)); + expect(dateFinder, findsNWidgets(history.length)); + expect(iconFinder, findsNWidgets(history.length)); + }); + + testWidgets( + 'should render correct icon if specified with icon selection function', + (widgetTester) async { + IconData iconDataSelector(String p0) => Icons.email; + + await widgetTester.pumpWidget(WrappedHistoryList( + history: history, + dateSelector: dateSelector, + entryDataSelector: entryDataSelector, + iconDataSelector: iconDataSelector, + )); + final circleIconFinder = find.byIcon(Icons.circle); + final correctIconFinder = find.byIcon(Icons.email); + + expect(circleIconFinder, findsNothing); + expect(correctIconFinder, findsNWidgets(history.length)); + }); + + testWidgets('icon field should overwrite iconSelector', + (widgetTester) async { + const IconData icon = Icons.abc; + IconData iconDataSelector(String p0) => Icons.email; + await widgetTester.pumpWidget(WrappedHistoryList( + history: history, + dateSelector: dateSelector, + entryDataSelector: entryDataSelector, + iconDataSelector: iconDataSelector, + icon: Icons.abc, + )); + final circleIconFinder = find.byIcon(Icons.circle); + final emailIconFinder = find.byIcon(Icons.email); + final correctIconFinder = find.byIcon(icon); + + expect(circleIconFinder, findsNothing); + expect(emailIconFinder, findsNothing); + expect(correctIconFinder, findsNWidgets(history.length)); + }); + + testWidgets( + 'EntryDetails should display ExpansionTiles if entryComment is given', + (widgetTester) async { + String entryCommentSelector(String p0) => ""; + await widgetTester.pumpWidget(WrappedHistoryList( + history: history, + dateSelector: dateSelector, + entryDataSelector: entryDataSelector, + entryCommentSelector: entryCommentSelector, + )); + final expansionTileFinder = find.byType(ExpansionTile); + expect(expansionTileFinder, findsNWidgets(history.length)); + }); + + testWidgets( + 'EntryDetails should display ExpansionTiles if entryComment is not given', + (widgetTester) async { + String entryCommentSelector(String p0) => ""; + await widgetTester.pumpWidget(WrappedHistoryList( + history: history, + dateSelector: dateSelector, + entryDataSelector: entryDataSelector, + entryCommentSelector: entryCommentSelector, + )); + final listTileFinder = find.byType(ListTile); + expect(listTileFinder, findsNWidgets(history.length)); + }); + }); +} + +class WrappedHistoryList extends StatelessWidget { + final List history; + final DateTime Function(T) dateSelector; + final String Function(T) entryDataSelector; + final IconData Function(T)? iconDataSelector; + final String Function(T)? entryCommentSelector; + final IconData? icon; + const WrappedHistoryList( + {super.key, + required this.history, + required this.dateSelector, + required this.entryDataSelector, + this.iconDataSelector, + this.icon, + this.entryCommentSelector}); + + @override + Widget build(BuildContext context) { + return MaterialApp( + home: Scaffold( + body: Column(children: [ + HistoryList( + history: history, + dateSelector: dateSelector, + entryDataSelector: entryDataSelector, + icon: icon, + iconDataSelector: iconDataSelector, + entryCommentSelector: entryCommentSelector, + ) + ]))); + } +} diff --git a/test/widget_tests/mute_button_test.dart b/test/widget_tests/mute_button_test.dart new file mode 100644 index 0000000..bd9d7fe --- /dev/null +++ b/test/widget_tests/mute_button_test.dart @@ -0,0 +1,34 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/audio_provider.dart'; +import 'package:smoke_cess_app/widgets/buttons/mute_button.dart'; + +void main() { + group('MuteButton', () { + testWidgets('should handle mute logic', (WidgetTester tester) async { + AudioProvider audioProvider = AudioProvider(); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider( + create: (context) => audioProvider, + child: const MuteButton()))), + ); + final button = find.byType(IconButton); + final mutedIcon = find.byIcon(Icons.volume_off_outlined); + final unMutedIcon = find.byIcon(Icons.volume_up_outlined); + expect(audioProvider.isMuted, false); + expect(button, findsOneWidget); + expect(mutedIcon, findsNothing); + expect(unMutedIcon, findsOneWidget); + + await tester.tap(unMutedIcon); + await tester.pump(); + + expect(audioProvider.isMuted, true); + expect(mutedIcon, findsOneWidget); + expect(unMutedIcon, findsNothing); + }); + }); +} diff --git a/test/widget_tests/scanner_test.dart b/test/widget_tests/scanner_test.dart new file mode 100644 index 0000000..3260472 --- /dev/null +++ b/test/widget_tests/scanner_test.dart @@ -0,0 +1,41 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:mobile_scanner/mobile_scanner.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/settings_provider.dart'; +import 'package:smoke_cess_app/widgets/buttons/text_icon_button.dart'; +import 'package:smoke_cess_app/widgets/scanner.dart'; + +import '../mock/settings_mock.dart'; + +void main() { + mockSharedPreferences(); + + group('MyScanner', () { + testWidgets('Scanner should start after Button is pressed', + (widgetTester) async { + SettingsProvider settingsProvider = SettingsProvider(); + expect(settingsProvider.scanning, false); + + await widgetTester.pumpWidget(MaterialApp( + home: Column( + children: [ + ChangeNotifierProvider( + create: (context) => settingsProvider, child: const MyScanner()) + ], + ), + )); + + final startScannerButton = find.byType(TextIconButton); + final scanner = find.byType(MobileScanner); + expect(startScannerButton, findsOneWidget); + expect(scanner, findsNothing); + + await widgetTester.tap(startScannerButton); + await widgetTester.pump(); + expect(startScannerButton, findsNothing); + expect(scanner, findsOneWidget); + expect(settingsProvider.scanning, true); + }); + }); +} diff --git a/test/widget_tests/timer_widget_test.dart b/test/widget_tests/timer_widget_test.dart new file mode 100644 index 0000000..2a4ef9b --- /dev/null +++ b/test/widget_tests/timer_widget_test.dart @@ -0,0 +1,30 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/timer_provider.dart'; +import 'package:smoke_cess_app/utils/timer_util.dart'; +import 'package:smoke_cess_app/widgets/timer_widget.dart'; + +void main() { + group('TimerWidget', () { + testWidgets('should display duration', (WidgetTester tester) async { + TimerProvider timerProvider = TimerProvider(); + Duration duration = const Duration(minutes: 1); + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: ChangeNotifierProvider( + create: (context) => timerProvider, + child: TimerWidget(duration: duration)))), + ); + + final durationTextFinder = find.text(formatTime(duration.inSeconds)); + expect(durationTextFinder, findsOneWidget); + + timerProvider.startTimer(duration); + await tester.pump(const Duration(seconds: 1)); + expect(durationTextFinder, findsNothing); + expect(find.text(formatTime(duration.inSeconds - 1)), findsOneWidget); + }); + }); +} diff --git a/test/widget_tests/widget_drop_down_test.dart b/test/widget_tests/widget_drop_down_test.dart new file mode 100644 index 0000000..a7f5f9f --- /dev/null +++ b/test/widget_tests/widget_drop_down_test.dart @@ -0,0 +1,47 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/widgets/drop_down.dart'; + +void main() { + testWidgets('DropDown should display items and update input model', + (WidgetTester tester) async { + // Define the list of items + final items = ['Item 1', 'Item 2', 'Item 3']; + + // Create an instance of the InputProvider and add it to the widget tree + final inputProvider = InputProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ], + child: MaterialApp( + home: Scaffold( + body: DropDown(items), + ), + ), + ), + ); + + // Verify that the DropDown displays the first item + expect(find.text(items[0]), findsOneWidget); + + // Tap the DropDown to open the menu + await tester.tap(find.byType(DropDown)); + await tester.pump(); + + // Verify that the menu displays the correct items + for (final item in items) { + expect(find.text(item).first, findsOneWidget); + } + + // Select the second item + await tester.tap(find.text(items[1]).last); + await tester.pump(); + + // Verify that the input model was updated with the selected item + expect(inputProvider.relapseCategory, equals(items[1])); + }); +} diff --git a/test/widget_tests/widget_elevated_card_test.dart b/test/widget_tests/widget_elevated_card_test.dart new file mode 100644 index 0000000..51e6242 --- /dev/null +++ b/test/widget_tests/widget_elevated_card_test.dart @@ -0,0 +1,71 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:smoke_cess_app/widgets/elevated_card.dart'; + +void main() { + group('ElevatedCard', () { + testWidgets('Renders the title and child', (WidgetTester tester) async { + // Arrange + const title = 'My Card Title'; + const childText = 'My Card Content'; + const child = Text(childText); + const card = ElevatedCard(title: title, child: child); + + // Act + await tester.pumpWidget(const MaterialApp(home: Scaffold(body: card))); + final titleFinder = find.text(title); + final childFinder = find.text(childText); + + // Assert + expect(titleFinder, findsOneWidget); + expect(childFinder, findsOneWidget); + }); + + testWidgets('Uses default title style', (WidgetTester tester) async { + // Arrange + final card = ElevatedCard(title: 'My Title', child: Container()); + + // Act + await tester.pumpWidget(MaterialApp(home: Scaffold(body: card))); + final titleFinder = find.text('My Title'); + final titleWidget = tester.widget(titleFinder); + + // Assert + expect(titleWidget.style?.fontSize, equals(16.0)); + expect(titleWidget.style?.fontWeight, equals(FontWeight.bold)); + }); + + testWidgets('Uses custom title style', (WidgetTester tester) async { + // Arrange + final card = ElevatedCard( + title: 'My Title', + child: Container(), + ); + + // Act + await tester.pumpWidget(MaterialApp(home: Scaffold(body: card))); + final titleFinder = find.text('My Title'); + final titleWidget = tester.widget(titleFinder); + + // Assert + expect(titleWidget.style?.fontSize, equals(16.0)); + expect(titleWidget.style?.fontWeight, equals(FontWeight.bold)); + }); + + testWidgets('Has rounded corners', (WidgetTester tester) async { + // Arrange + final card = ElevatedCard(title: 'My Title', child: Container()); + + // Act + await tester.pumpWidget(MaterialApp(home: Scaffold(body: card))); + final cardFinder = find.byType(Card); + final cardWidget = tester.widget(cardFinder); + + // Assert + expect( + cardWidget.shape, + const RoundedRectangleBorder( + borderRadius: BorderRadius.all(Radius.circular(16.0)))); + }); + }); +} diff --git a/test/widget_tests/widget_entry_detail_test.dart b/test/widget_tests/widget_entry_detail_test.dart new file mode 100644 index 0000000..a2d3f5a --- /dev/null +++ b/test/widget_tests/widget_entry_detail_test.dart @@ -0,0 +1,43 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:intl/date_symbol_data_local.dart'; +import 'package:smoke_cess_app/widgets/entry_detail_widget.dart'; + +void main() { + initializeDateFormatting('de'); + group('EntryDetail', () { + testWidgets('should use ExpansionTile if Comments is set', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: EntryDetail( + date: DateTime.now(), + entryComment: 'A comment', + entryData: 'Test', + iconData: Icons.plus_one, + ), + )), + ); + + expect(find.byType(ExpansionTile), findsOneWidget); + }); + + testWidgets('should use ListTile if Comments is null', + (WidgetTester tester) async { + await tester.pumpWidget( + MaterialApp( + home: Scaffold( + body: EntryDetail( + date: DateTime.now(), + entryComment: 'A comment', + entryData: 'Test', + iconData: Icons.plus_one, + ), + )), + ); + + expect(find.byType(ListTile), findsOneWidget); + }); + }); +} diff --git a/test/widget_tests/widget_slider_test.dart b/test/widget_tests/widget_slider_test.dart new file mode 100644 index 0000000..1dc470d --- /dev/null +++ b/test/widget_tests/widget_slider_test.dart @@ -0,0 +1,117 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/widgets/slider.dart'; + +void main() { + testWidgets('Slider starts at 50', (WidgetTester tester) async { + // Create an instance of the InputProvider and add it to the widget tree + final inputProvider = InputProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MySlider(), + ), + ), + ), + ); + + // Verify that the Slider displays 50 + expect(find.text('50'), findsOneWidget); + }); + + testWidgets('Slider puts 51 after Tap on plus in InputProvider', + (WidgetTester tester) async { + final inputProvider = InputProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MySlider(), + ), + ), + ), + ); + + await tester.tap(find.byIcon(Icons.add_outlined)); + await tester.pump(); + + expect(inputProvider.sliderValue, equals(51)); + }); + + testWidgets('Slider puts 49 after Tap on subtract in InputProvider', + (WidgetTester tester) async { + final inputProvider = InputProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MySlider(), + ), + ), + ), + ); + + await tester.tap(find.byIcon(Icons.remove_outlined)); + await tester.pump(); + + expect(inputProvider.sliderValue, equals(49)); + }); + + testWidgets('Slider doesnt go higher than 100', (WidgetTester tester) async { + final inputProvider = InputProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MySlider(), + ), + ), + ), + ); + + for (int i = 0; i < 100; i++) { + await tester.tap(find.byIcon(Icons.add_outlined)); + await tester.pump(); + } + + expect(inputProvider.sliderValue, equals(100)); + }); + + testWidgets('Slider doesnt go lower than 0', (WidgetTester tester) async { + final inputProvider = InputProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MySlider(), + ), + ), + ), + ); + + for (int i = 0; i < 100; i++) { + await tester.tap(find.byIcon(Icons.remove_outlined)); + await tester.pump(); + } + + expect(inputProvider.sliderValue, equals(0)); + }); +} diff --git a/test/widget_tests/widget_text_formfield_test.dart b/test/widget_tests/widget_text_formfield_test.dart new file mode 100644 index 0000000..84aa134 --- /dev/null +++ b/test/widget_tests/widget_text_formfield_test.dart @@ -0,0 +1,51 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:provider/provider.dart'; +import 'package:smoke_cess_app/providers/input_provider.dart'; +import 'package:smoke_cess_app/widgets/text_formfield.dart'; + +void main() { + testWidgets('TextFormField initial Value is correct', + (WidgetTester tester) async { + const String testText = 'Its a test'; + // Create an instance of the InputProvider and add it to the widget tree + final inputProvider = InputProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MyTextFormField(testText), + ), + ), + ), + ); + + // Verify that the Slider displays 50 + expect(find.text(testText), findsOneWidget); + }); + + testWidgets('TextFormField inputs correctly to Provider', + (WidgetTester tester) async { + const String testText = 'Its a test'; + // Create an instance of the InputProvider and add it to the widget tree + final inputProvider = InputProvider(); + await tester.pumpWidget( + MultiProvider( + providers: [ + ChangeNotifierProvider.value(value: inputProvider), + ], + child: const MaterialApp( + home: Scaffold( + body: MyTextFormField(testText), + ), + ), + ), + ); + + await tester.enterText(find.byType(TextField), testText); + expect(inputProvider.textController.text, testText); + }); +} diff --git a/test/widget_tests/widget_todo_icon_test.dart b/test/widget_tests/widget_todo_icon_test.dart new file mode 100644 index 0000000..077308f --- /dev/null +++ b/test/widget_tests/widget_todo_icon_test.dart @@ -0,0 +1,21 @@ +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:smoke_cess_app/widgets/todo_icon.dart'; + +void main() { + testWidgets('MyToDoIcon has a red dot', (WidgetTester tester) async { + // Build the widget tree + await tester.pumpWidget( + const MaterialApp( + home: Scaffold( + body: MyToDoIcon( + Icon(Icons.check), + ), + ), + ), + ); + + // Verify that the red dot is present + expect(find.byIcon(Icons.brightness_1), findsOneWidget); + }); +}