Merge branch 'testing_branch' into 'main'

Testing branch

See merge request Crondung/hsma_cpd!37
main
Julian Gegner 2023-03-06 23:08:41 +00:00
commit 748c1cc585
36 changed files with 1373 additions and 44 deletions

View File

@ -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

View File

@ -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;

View File

@ -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();
}

View File

@ -21,7 +21,7 @@ class SettingsProvider extends ChangeNotifier {
initSettings();
}
void initSettings() async {
Future<void> initSettings() async {
_settings = await loadSettings();
_initialized = _settings != null ? true : false;
notifyListeners();

View File

@ -31,7 +31,7 @@ class TasksProvider extends ChangeNotifier {
notifyListeners();
}
void initHistories() async {
Future<void> initHistories() async {
moodHistory = await globals.databaseService.getMoodRecords();
sleepHistory = await globals.databaseService.getSleepRecords();
workoutHistory = await globals.databaseService.getWorkoutRecords();

View File

@ -100,25 +100,25 @@ class WorkoutProvider extends ChangeNotifier {
Map<WorkoutPhases, Map<String, dynamic>> _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
}

View File

@ -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,

View File

@ -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");

View File

@ -13,8 +13,8 @@ class MoodForm extends StatelessWidget {
@override
Widget build(BuildContext context) {
var inputModel = context.watch<InputProvider>();
var tasksModel = context.watch<TasksProvider>();
InputProvider inputModel = context.watch<InputProvider>();
TasksProvider tasksModel = context.watch<TasksProvider>();
return ListView(
padding: const EdgeInsets.fromLTRB(10, 10, 10, 10),
children: [

View File

@ -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,
)

View File

@ -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);
});
}

View File

@ -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);
});
}

View File

@ -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]);
});
}

View File

@ -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);
});
}

View File

@ -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));
});
}

View File

@ -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);
});
}

View File

@ -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) {}
}

View File

@ -0,0 +1,22 @@
import 'package:shared_preferences/shared_preferences.dart';
Map<String, Object> 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);

View File

@ -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<TZDateTime> tzDateTimes = createTZDateTimes(null, 10, 0);
expect(tzDateTimes, []);
});
test('returns empty list when selectedHours is null', () {
List<TZDateTime> tzDateTimes = createTZDateTimes(['Montag'], null, 0);
expect(tzDateTimes, []);
});
test('returns empty list when selectedMinutes is null', () {
List<TZDateTime> tzDateTimes = createTZDateTimes(['Montag'], 10, null);
expect(tzDateTimes, []);
});
test('returns empty list when no valid dates are found', () {
List<TZDateTime> tzDateTimes = createTZDateTimes(['Mong'], 10, 0);
expect(tzDateTimes, []);
});
test('returns list of valid dates', () {
List<TZDateTime> 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<String> selectedDays = [
weekDays.keys.elementAt(DateTime.now().weekday - 1)
];
List<TZDateTime> 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<TZDateTime> 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<List<TZDateTime>>());
});
});
}

View File

@ -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)));
});
});
}

View File

@ -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);
});
});
}

View File

@ -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);
});
}

View File

@ -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));
});
}

View File

@ -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);
});
});
}

View File

@ -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);
});
}

View File

@ -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<String> 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<T> extends StatelessWidget {
final List<T> 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<T>(
history: history,
dateSelector: dateSelector,
entryDataSelector: entryDataSelector,
icon: icon,
iconDataSelector: iconDataSelector,
entryCommentSelector: entryCommentSelector,
)
])));
}
}

View File

@ -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);
});
});
}

View File

@ -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);
});
});
}

View File

@ -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);
});
});
}

View File

@ -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]));
});
}

View File

@ -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<Text>(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<Text>(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<Card>(cardFinder);
// Assert
expect(
cardWidget.shape,
const RoundedRectangleBorder(
borderRadius: BorderRadius.all(Radius.circular(16.0))));
});
});
}

View File

@ -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);
});
});
}

View File

@ -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));
});
}

View File

@ -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);
});
}

View File

@ -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);
});
}