Commit f6a08a9
Changed files (7)
lib/model/storage/convert_util.dart
@@ -21,6 +21,7 @@ class ConvertUtil {
static double? parseDouble(dynamic value) {
if (value is double) return value;
+ if (value is int) return value.toDouble();
if (value is String) return double.tryParse(value);
return null;
}
@@ -39,7 +40,8 @@ class ConvertUtil {
static Locale? parseLocale(dynamic value) {
if (value == 'NULL') return null;
- return Locale(value);
+ if (value is String) return Locale(value);
+ return null;
}
static MaterialColor? parseMaterialColor(dynamic value) {
@@ -56,7 +58,6 @@ class ConvertUtil {
if (parsedValue == null) return null;
color = Color(parsedValue);
} else {
- assert(false);
return null;
}
lib/model/storage/export_csv_settings_store.dart
@@ -20,7 +20,13 @@ class CsvExportSettings extends ChangeNotifier {
exportHeadline: ConvertUtil.parseBool(map['exportHeadline'])
);
- factory CsvExportSettings.fromJson(String json) => CsvExportSettings.fromMap(jsonDecode(json));
+ factory CsvExportSettings.fromJson(String json) {
+ try {
+ return CsvExportSettings.fromMap(jsonDecode(json));
+ } catch (exception) {
+ return CsvExportSettings();
+ }
+ }
Map<String, dynamic> toMap() => <String, dynamic>{
'fieldDelimiter': fieldDelimiter,
lib/model/storage/export_pdf_settings_store.dart
@@ -33,7 +33,13 @@ class PdfExportSettings extends ChangeNotifier {
cellFontSize: ConvertUtil.parseDouble(map['cellFontSize']),
);
- factory PdfExportSettings.fromJson(String json) => PdfExportSettings.fromMap(jsonDecode(json));
+ factory PdfExportSettings.fromJson(String json) {
+ try {
+ return PdfExportSettings.fromMap(jsonDecode(json));
+ } catch (exception) {
+ return PdfExportSettings();
+ }
+ }
Map<String, dynamic> toMap() => <String, dynamic>{
'exportTitle': exportTitle,
lib/model/storage/export_settings_store.dart
@@ -20,7 +20,13 @@ class ExportSettings extends ChangeNotifier {
exportAfterEveryEntry: ConvertUtil.parseBool(map['exportAfterEveryEntry'])
);
- factory ExportSettings.fromJson(String json) => ExportSettings.fromMap(jsonDecode(json));
+ factory ExportSettings.fromJson(String json) {
+ try {
+ return ExportSettings.fromMap(jsonDecode(json));
+ } catch (exception) {
+ return ExportSettings();
+ }
+ }
Map<String, dynamic> toMap() => <String, dynamic>{
'exportFormat': exportFormat.serialize(),
lib/model/storage/settings_store.dart
@@ -15,6 +15,7 @@ import 'package:flutter/material.dart';
/// - [ ] Add parsable representation (string, boolean or integer) to the .toMap
/// - [ ] Parse it in the .fromMap factory method
/// - [ ] Make sure edge cases are handled in .fromMap (does not exist (update), not parsable (user))
+/// - [ ] To verify everything was done correctly, tests should be expanded with the newly added fields (json_serialization_test.dart)
class Settings extends ChangeNotifier {
/// Creates a settings object with the default values.
///
@@ -82,8 +83,14 @@ class Settings extends ChangeNotifier {
useLegacyList: ConvertUtil.parseBool(map['useLegacyList']),
language: ConvertUtil.parseLocale(map['language'])
);
-
- factory Settings.fromJson(String json) => Settings.fromMap(jsonDecode(json));
+
+ factory Settings.fromJson(String json) {
+ try {
+ return Settings.fromMap(jsonDecode(json));
+ } catch (exception) {
+ return Settings();
+ }
+ }
Map<String, dynamic> toMap() => <String, dynamic>{
'accentColor': accentColor.value,
@@ -99,6 +106,7 @@ class Settings extends ChangeNotifier {
'confirmDeletion': confirmDeletion,
'darkMode': darkMode,
'followSystemDarkMode': followSystemDarkMode,
+ 'validateInputs': validateInputs,
'allowMissingValues': allowMissingValues,
'drawRegressionLines': drawRegressionLines,
'startWithAddMeasurementPage': startWithAddMeasurementPage,
test/model/intervall_store_test.dart
@@ -33,61 +33,6 @@ void main() {
));
});
- test('should create json without error', () {
- final intervall = IntervallStorage(stepSize: TimeStep.year);
- final json = intervall.toJson();
- expect(json.length, greaterThan(0));
- });
-
- test('should load same data from json', () {
- final initialData = IntervallStorage();
- final json = initialData.toJson();
- final recreatedData = IntervallStorage.fromJson(json);
-
- expect(initialData.stepSize, recreatedData.stepSize);
- expect(initialData.currentRange.start.millisecondsSinceEpoch,
- recreatedData.currentRange.start.millisecondsSinceEpoch);
- expect(initialData.currentRange.end.millisecondsSinceEpoch,
- recreatedData.currentRange.end.millisecondsSinceEpoch);
- });
-
- test('should load same data from json in edge cases', () {
- final initialData = IntervallStorage(stepSize: TimeStep.month, range: DateTimeRange(
- start: DateTime.fromMillisecondsSinceEpoch(1234),
- end: DateTime.fromMillisecondsSinceEpoch(5678)
- ));
- final json = initialData.toJson();
- final recreatedData = IntervallStorage.fromJson(json);
-
- expect(initialData.stepSize, TimeStep.month);
- expect(recreatedData.currentRange.start.millisecondsSinceEpoch, 1234);
- expect(recreatedData.currentRange.end.millisecondsSinceEpoch, 5678);
- });
-
- test('should not crash when parsing incorrect json', () {
- IntervallStorage.fromJson('banana');
- IntervallStorage.fromJson('{"stepSize" = 1}');
- IntervallStorage.fromJson('{"stepSize": 1');
- IntervallStorage.fromJson('{stepSize: 1}');
- IntervallStorage.fromJson('green{stepSize: 1}');
- });
-
- test('should not crash when parsing invalid values and ignore them', () {
- final v1 = IntervallStorage.fromJson('{"stepSize": true}');
- final v2 = IntervallStorage.fromJson('{"stepSize": "month"}');
- final v3 = IntervallStorage.fromJson('{"start": "month", "end": 10.5}');
- final v4 = IntervallStorage.fromJson('{"start": 18.6, "end": 90.65}');
-
- expect(v1.stepSize, TimeStep.last7Days);
- expect(v2.stepSize, TimeStep.last7Days);
- expect(v3.stepSize, TimeStep.last7Days);
-
- // in minutes to avoid failing through performance
- expect(v2.currentRange.duration.inMinutes, v1.currentRange.duration.inMinutes);
- expect(v3.currentRange.duration.inMinutes, v1.currentRange.duration.inMinutes);
- expect(v4.currentRange.duration.inMinutes, v1.currentRange.duration.inMinutes);
- });
-
test('intervall lengths should match step size', () {
final dayIntervall = IntervallStorage(stepSize: TimeStep.day);
test/model/json_serialization_test.dart
@@ -0,0 +1,250 @@
+
+import 'package:blood_pressure_app/model/storage/export_csv_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_pdf_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/export_settings_store.dart';
+import 'package:blood_pressure_app/model/storage/intervall_store.dart';
+import 'package:blood_pressure_app/model/storage/settings_store.dart';
+import 'package:flutter/material.dart';
+import 'package:flutter_test/flutter_test.dart';
+
+void main() {
+ group('IntervallStorage', () {
+ test('should create json without error', () {
+ final intervall = IntervallStorage(stepSize: TimeStep.year);
+ final json = intervall.toJson();
+ expect(json.length, greaterThan(0));
+ });
+
+ test('should load same data from json', () {
+ final initialData = IntervallStorage();
+ final json = initialData.toJson();
+ final recreatedData = IntervallStorage.fromJson(json);
+
+ expect(initialData.stepSize, recreatedData.stepSize);
+ expect(initialData.currentRange.start.millisecondsSinceEpoch,
+ recreatedData.currentRange.start.millisecondsSinceEpoch);
+ expect(initialData.currentRange.end.millisecondsSinceEpoch,
+ recreatedData.currentRange.end.millisecondsSinceEpoch);
+ });
+
+ test('should load same data from json in edge cases', () {
+ final initialData = IntervallStorage(stepSize: TimeStep.month, range: DateTimeRange(
+ start: DateTime.fromMillisecondsSinceEpoch(1234),
+ end: DateTime.fromMillisecondsSinceEpoch(5678)
+ ));
+ final json = initialData.toJson();
+ final recreatedData = IntervallStorage.fromJson(json);
+
+ expect(initialData.stepSize, TimeStep.month);
+ expect(recreatedData.currentRange.start.millisecondsSinceEpoch, 1234);
+ expect(recreatedData.currentRange.end.millisecondsSinceEpoch, 5678);
+ });
+
+ test('should not crash when parsing incorrect json', () {
+ IntervallStorage.fromJson('banana');
+ IntervallStorage.fromJson('{"stepSize" = 1}');
+ IntervallStorage.fromJson('{"stepSize": 1');
+ IntervallStorage.fromJson('{stepSize: 1}');
+ IntervallStorage.fromJson('green{stepSize: 1}');
+ });
+
+ test('should not crash when parsing invalid values and ignore them', () {
+ final v1 = IntervallStorage.fromJson('{"stepSize": true}');
+ final v2 = IntervallStorage.fromJson('{"stepSize": "month"}');
+ final v3 = IntervallStorage.fromJson('{"start": "month", "end": 10.5}');
+ final v4 = IntervallStorage.fromJson('{"start": 18.6, "end": 90.65}');
+
+ expect(v1.stepSize, TimeStep.last7Days);
+ expect(v2.stepSize, TimeStep.last7Days);
+ expect(v3.stepSize, TimeStep.last7Days);
+
+ // in minutes to avoid failing through performance
+ expect(v2.currentRange.duration.inMinutes, v1.currentRange.duration.inMinutes);
+ expect(v3.currentRange.duration.inMinutes, v1.currentRange.duration.inMinutes);
+ expect(v4.currentRange.duration.inMinutes, v1.currentRange.duration.inMinutes);
+ });
+ });
+
+ group('Settings', (){
+ test('should be able to recreate all values from json', () {
+ final initial = Settings(
+ language: const Locale('en'),
+ accentColor: Colors.deepOrange,
+ sysColor: Colors.deepOrange,
+ diaColor: Colors.deepOrange,
+ pulColor: Colors.deepOrange,
+ dateFormatString: 'Lorem Ipsum',
+ graphLineThickness: 134.23123,
+ animationSpeed: 78,
+ sysWarn: 78,
+ diaWarn: 78,
+ allowManualTimeInput: false,
+ confirmDeletion: false,
+ darkMode: false,
+ followSystemDarkMode: false,
+ validateInputs: false,
+ allowMissingValues: false,
+ drawRegressionLines: false,
+ startWithAddMeasurementPage: false,
+ useLegacyList: false,
+ );
+ final fromJson = Settings.fromJson(initial.toJson());
+
+ expect(initial.language, fromJson.language);
+ expect(initial.accentColor.value, fromJson.accentColor.value);
+ expect(initial.sysColor.value, fromJson.sysColor.value);
+ expect(initial.diaColor.value, fromJson.diaColor.value);
+ expect(initial.pulColor.value, fromJson.pulColor.value);
+ expect(initial.dateFormatString, fromJson.dateFormatString);
+ expect(initial.graphLineThickness, fromJson.graphLineThickness);
+ expect(initial.animationSpeed, fromJson.animationSpeed);
+ expect(initial.sysWarn, fromJson.sysWarn);
+ expect(initial.diaWarn, fromJson.diaWarn);
+ expect(initial.allowManualTimeInput, fromJson.allowManualTimeInput);
+ expect(initial.confirmDeletion, fromJson.confirmDeletion);
+ expect(initial.darkMode, fromJson.darkMode);
+ expect(initial.followSystemDarkMode, fromJson.followSystemDarkMode);
+ expect(initial.validateInputs, fromJson.validateInputs);
+ expect(initial.allowMissingValues, fromJson.allowMissingValues);
+ expect(initial.drawRegressionLines, fromJson.drawRegressionLines);
+ expect(initial.startWithAddMeasurementPage, fromJson.startWithAddMeasurementPage);
+ expect(initial.useLegacyList, fromJson.useLegacyList);
+
+ expect(initial.toJson(), fromJson.toJson());
+ });
+
+ test('should not crash when parsing incorrect json', () {
+ Settings.fromJson('banana');
+ Settings.fromJson('{"stepSize" = 1}');
+ Settings.fromJson('{"stepSize": 1');
+ Settings.fromJson('{stepSize: 1}');
+ Settings.fromJson('green{stepSize: 1}');
+ });
+
+ test('should not crash when parsing invalid values and ignore them', () {
+ final v1 = Settings.fromJson('{"pulColor": true}');
+ final v2 = Settings.fromJson('{"validateInputs": "red"}');
+ final v3 = Settings.fromJson('{"validateInputs": "month", "useLegacyList": 10.5}');
+ final v4 = Settings.fromJson('{"sysWarn": 18.6, "diaWarn": 90.65}');
+
+ expect(v1.pulColor.value, Settings().pulColor.value);
+ expect(v2.validateInputs, Settings().validateInputs);
+ expect(v3.useLegacyList, Settings().useLegacyList);
+ });
+ });
+
+ group('ExportSettings', (){
+ test('should be able to recreate all values from json', () {
+ final initial = ExportSettings(
+ exportFormat: ExportFormat.db,
+ defaultExportDir: 'lorem ipsum',
+ exportAfterEveryEntry: true,
+ );
+ final fromJson = ExportSettings.fromJson(initial.toJson());
+
+ expect(initial.exportFormat, fromJson.exportFormat);
+ expect(initial.defaultExportDir, fromJson.defaultExportDir);
+ expect(initial.exportAfterEveryEntry, fromJson.exportAfterEveryEntry);
+
+ expect(initial.toJson(), fromJson.toJson());
+ });
+
+ test('should not crash when parsing incorrect json', () {
+ ExportSettings.fromJson('banana');
+ ExportSettings.fromJson('{"defaultExportDir" = 1}');
+ ExportSettings.fromJson('{"defaultExportDir": 1');
+ ExportSettings.fromJson('{defaultExportDir: 1}');
+ ExportSettings.fromJson('green{exportFormat: 1}');
+ });
+
+ test('should not crash when parsing invalid values and ignore them', () {
+ final v1 = ExportSettings.fromJson('{"defaultExportDir": ["test"]}');
+ final v2 = ExportSettings.fromJson('{"exportFormat": "red"}');
+ final v3 = ExportSettings.fromJson('{"exportFormat": "month", "exportAfterEveryEntry": 15}');
+
+ expect(v1.defaultExportDir, ExportSettings().defaultExportDir);
+ expect(v2.exportFormat, ExportSettings().exportFormat);
+ expect(v3.exportFormat, ExportSettings().exportFormat);
+ expect(v3.exportAfterEveryEntry, ExportSettings().exportAfterEveryEntry);
+ });
+ });
+
+ group('CsvExportSettings', (){
+ test('should be able to recreate all values from json', () {
+ final initial = CsvExportSettings(
+ fieldDelimiter: 'asdfghjklö',
+ textDelimiter: 'asdfghjklö2',
+ exportHeadline: false,
+ );
+ final fromJson = CsvExportSettings.fromJson(initial.toJson());
+
+ expect(initial.fieldDelimiter, fromJson.fieldDelimiter);
+ expect(initial.textDelimiter, fromJson.textDelimiter);
+ expect(initial.exportHeadline, fromJson.exportHeadline);
+
+ expect(initial.toJson(), fromJson.toJson());
+ });
+
+ test('should not crash when parsing incorrect json', () {
+ CsvExportSettings.fromJson('banana');
+ CsvExportSettings.fromJson('{"fieldDelimiter" = 1}');
+ CsvExportSettings.fromJson('{"fieldDelimiter": 1');
+ CsvExportSettings.fromJson('{fieldDelimiter: 1}');
+ CsvExportSettings.fromJson('green{fieldDelimiter: 1}');
+ });
+
+ test('should not crash when parsing invalid values and ignore them', () {
+ final v1 = CsvExportSettings.fromJson('{"fieldDelimiter": ["test"]}');
+ final v2 = CsvExportSettings.fromJson('{"exportHeadline": "red"}');
+ final v3 = CsvExportSettings.fromJson('{"textDelimiter": "month", "textDelimiter": {"test": 10.5}}');
+
+ expect(v1.fieldDelimiter, CsvExportSettings().fieldDelimiter);
+ expect(v2.exportHeadline, CsvExportSettings().exportHeadline);
+ expect(v3.textDelimiter, CsvExportSettings().textDelimiter);
+ });
+ });
+
+ group('PdfExportSettings', (){
+ test('should be able to recreate all values from json', () {
+ final initial = PdfExportSettings(
+ exportTitle: false,
+ exportStatistics: false,
+ exportData: false,
+ headerHeight: 67.89,
+ cellHeight: 67.89,
+ headerFontSize: 67.89,
+ cellFontSize: 67.89,
+ );
+ final fromJson = PdfExportSettings.fromJson(initial.toJson());
+
+ expect(initial.exportTitle, fromJson.exportTitle);
+ expect(initial.exportStatistics, fromJson.exportStatistics);
+ expect(initial.exportData, fromJson.exportData);
+ expect(initial.headerHeight, fromJson.headerHeight);
+ expect(initial.cellHeight, fromJson.cellHeight);
+ expect(initial.headerFontSize, fromJson.headerFontSize);
+ expect(initial.cellFontSize, fromJson.cellFontSize);
+
+ expect(initial.toJson(), fromJson.toJson());
+ });
+
+ test('should not crash when parsing incorrect json', () {
+ PdfExportSettings.fromJson('banana');
+ PdfExportSettings.fromJson('{"cellFontSize" = 1}');
+ PdfExportSettings.fromJson('{"cellFontSize": 1');
+ PdfExportSettings.fromJson('{cellFontSize: 1}');
+ PdfExportSettings.fromJson('green{fieldDelimiter: 1}');
+ });
+
+ test('should not crash when parsing invalid values and ignore them', () {
+ final v1 = PdfExportSettings.fromJson('{"cellFontSize": ["test"]}');
+ final v2 = PdfExportSettings.fromJson('{"cellFontSize": "red"}');
+ final v3 = PdfExportSettings.fromJson('{"headerFontSize": "month", "exportData": 15}');
+
+ expect(v1.cellFontSize, PdfExportSettings().cellFontSize);
+ expect(v2.cellFontSize, PdfExportSettings().cellFontSize);
+ expect(v3.headerFontSize, PdfExportSettings().headerFontSize);
+ expect(v3.exportData, PdfExportSettings().exportData);
+ });
+ });
+}
\ No newline at end of file