diff --git a/lib/hmi_core.dart b/lib/hmi_core.dart index 8c45aef..2879cbe 100644 --- a/lib/hmi_core.dart +++ b/lib/hmi_core.dart @@ -8,8 +8,6 @@ export 'src/core/entities/ds_data_type.dart'; export 'src/core/entities/ds_status.dart'; export 'src/core/entities/ds_timestamp.dart'; export 'src/core/entities/state_constatnts.dart'; -export 'src/core/entities/alarm_colors.dart'; -export 'src/core/entities/state_colors.dart'; export 'src/core/entities/stacked.dart'; // RelativeValue export 'src/core/relative_value.dart'; diff --git a/lib/hmi_core_alarm_colors.dart b/lib/hmi_core_alarm_colors.dart deleted file mode 100644 index 298ca09..0000000 --- a/lib/hmi_core_alarm_colors.dart +++ /dev/null @@ -1,3 +0,0 @@ -library hmi_core_alarm_colors; - -export 'src/core/entities/alarm_colors.dart'; \ No newline at end of file diff --git a/lib/hmi_core_entities.dart b/lib/hmi_core_entities.dart index f0b348e..ed127e3 100644 --- a/lib/hmi_core_entities.dart +++ b/lib/hmi_core_entities.dart @@ -8,6 +8,4 @@ export 'src/core/entities/ds_data_type.dart'; export 'src/core/entities/ds_status.dart'; export 'src/core/entities/ds_timestamp.dart'; export 'src/core/entities/state_constatnts.dart'; -export 'src/core/entities/alarm_colors.dart'; -export 'src/core/entities/state_colors.dart'; export 'src/core/entities/stacked.dart'; diff --git a/lib/hmi_core_option.dart b/lib/hmi_core_option.dart index 8d589bd..7e6fb7c 100644 --- a/lib/hmi_core_option.dart +++ b/lib/hmi_core_option.dart @@ -1,3 +1,6 @@ library hmi_core_option; -export 'src/core/option/option.dart'; \ No newline at end of file +export 'src/core/option/option.dart'; +export 'src/core/option/option_extract_extension.dart'; +export 'src/core/option/option_transform_extension.dart'; +export 'src/core/option/option_querying_extension.dart'; diff --git a/lib/hmi_core_state_colors.dart b/lib/hmi_core_state_colors.dart deleted file mode 100644 index 33b3fca..0000000 --- a/lib/hmi_core_state_colors.dart +++ /dev/null @@ -1,3 +0,0 @@ -library hmi_core_state_colors; - -export 'src/core/entities/state_colors.dart'; \ No newline at end of file diff --git a/lib/src/app_settings/app_settings.dart b/lib/src/app_settings/app_settings.dart index 6e480d0..8de4ad5 100644 --- a/lib/src/app_settings/app_settings.dart +++ b/lib/src/app_settings/app_settings.dart @@ -1,11 +1,20 @@ +import 'dart:convert'; import 'package:hmi_core/src/core/error/failure.dart'; import 'package:hmi_core/src/core/json/json_map.dart'; import 'package:hmi_core/src/core/log/log.dart'; -import 'package:hmi_core/src/core/result/result.dart'; +import 'package:hmi_core/hmi_core_text_file.dart'; +import 'package:hmi_core/src/core/result/result_transform_extension.dart'; /// +/// Stores application settings. +/// +/// Settings can be accessed for reading and writing by its keys. +/// +/// For key names prefer to use kebab-case notation, with unique prefixes +/// for different groups of settings. E.g. `ui-padding`, `ui-font-size`, +/// `api-host`, `api-port`, etc. class AppSettings { - static const _log = Log('AppSettings'); - static final _map = { + static const _log = Log('AppSettings '); + static final _settings = { 'displaySizeWidth': 1024, 'displaySizeHeight': 768, // Place Durations in milliseconds! @@ -18,25 +27,138 @@ class AppSettings { 'floatingActionButtonSize': 60.0, 'floatingActionIconSize': 45.0, }; + static final _isWritable = {}; + static TextFile _store = const TextFile.path('stored_settings.json'); + /// + /// Initializes app settings with [readOnly] and [writable] settings. + /// + /// [readOnly] settings are accessible for reading only. /// - static Future initialize({JsonMap? jsonMap}) async { - if (jsonMap != null) { - await jsonMap.decoded - .then((result) => switch(result) { - Ok(value: final map) => _map.addAll(map), - Err() => _log.warning('Failed to initialize app settings from file.'), - }); + /// [writable] settings are accessible for reading and updating (writing). + /// + /// [store] file is used to write and restore [writable] settings. + /// If not passed, file with path `stored_settings.json` will be used + /// by default. + static Future initialize({ + JsonMap readOnly = const JsonMap.empty(), + JsonMap writable = const JsonMap.empty(), + TextFile? store, + }) async { + if (store != null) { + _store = store; } + _log.info('Initializing read-only app settings...'); + await _parseSettings( + readOnly, + onSettingParsed: (entry) { + if (_settings.containsKey(entry.key)) { + _log.warning('Setting with key "${entry.key}" will be overwritten.'); + } + _settings[entry.key] = entry.value; + _isWritable[entry.key] = false; + _log.info('Added read-only setting "${entry.key}": ${entry.value}.'); + }, + ); + _log.info('Initializing writable app settings...'); + await _parseSettings( + writable, + onSettingParsed: (entry) { + if (_settings.containsKey(entry.key)) { + _log.warning('Setting with key "${entry.key}" will be overwritten.'); + } + _settings[entry.key] = entry.value; + _isWritable[entry.key] = true; + _log.info('Added writeable setting "${entry.key}": ${entry.value}.'); + }, + ); + _log.info('Restoring writable app settings...'); + await _parseSettings( + JsonMap.fromTextFile(_store), + onSettingParsed: (entry) { + if (!_canWriteSetting(entry.key)) { + _log.warning('Writeable setting with key "${entry.key}" does not exist and was ignored.'); + } else { + _settings[entry.key] = entry.value; + _log.info('Restored writeable setting "${entry.key}": ${entry.value}.'); + } + }, + ); + } + // + static Future _parseSettings( + JsonMap settings, { + void Function(MapEntry)? onSettingParsed, + }) async { + final decodedResult = await settings.decoded; + decodedResult + .inspect((map) { + for (final entry in map.entries) { + onSettingParsed?.call(entry); + } + }).inspectErr((error) { + _log.warning('Failed to initialize app settings, ${error.message}.'); + }); + } + // + static bool _canWriteSetting(String key) { + return _settings.containsKey(key) && _isWritable.containsKey(key) && (_isWritable[key] ?? false); } /// - static dynamic getSetting(String settingName) { - final setting = _map[settingName]; - if (setting == null) { - throw Failure( - message: 'Ошибка в методе $AppSettings.getSetting(): Не найдена настройка "$settingName"', - stackTrace: StackTrace.current, - ); + /// Returns value of app setting by [key]. + /// + /// Calls [onError] that can return other value based on passed [Failure]. + static dynamic getSetting( + String key, { + dynamic Function(Failure err)? onError, + }) { + if (!_settings.containsKey(key)) { + final err = Failure('$AppSettings.getSetting | Not found key "$key"'); + if (onError != null) { + return onError(err); + } + throw err; + } + return _settings[key]; + } + /// + /// Updates app setting with key [key] to new [value] + /// and save it asynchronously to file. + /// + /// Calls [onSuccess] or [onError] when setting will be updated + /// and saved successfully or with error. + static Future setSetting( + String key, + dynamic value, { + void Function(Failure error)? onError, + void Function()? onSuccess, + }) async { + if (!_canWriteSetting(key)) { + final failure = Failure('AppSettings.setSetting | Setting "$key" - is not writable, can\'t be updated'); + onError?.call(failure); + } else { + final writableSettings = _getWritableSettings(); + writableSettings[key] = value; + await _store + .write(json.encode(writableSettings)).then( + (_) { + _settings[key] = value; + onSuccess?.call(); + }, + ).catchError( + (error, stackTrace) { + final failure = Failure('AppSettings.setSetting | Failed to save setting "$key", $error.'); + _log.warning(failure); + onError?.call(failure); + }, + ); } - return setting; + } + // + static Map _getWritableSettings() { + return Map.fromEntries( + _settings.entries.where( + (entry) => _canWriteSetting(entry.key), + ), + ); } } diff --git a/lib/src/app_settings/setting.dart b/lib/src/app_settings/setting.dart index bf59da1..73943d7 100644 --- a/lib/src/app_settings/setting.dart +++ b/lib/src/app_settings/setting.dart @@ -1,48 +1,72 @@ import 'app_settings.dart'; - +import 'package:hmi_core/src/core/error/failure.dart'; /// -/// Holds setting value stored in AppSettings by it name -/// - value can be returned in int, double or string represantation +/// Holds setting value stored in [AppSettings] by it name. +/// +/// Value can be returned in int, double or string representation. class Setting { final String _name; final double _factor; + final dynamic Function(Failure err)? _onError; + /// + /// Holds setting value stored in [AppSettings] by it [name]. + /// + /// Value can be returned in int, double or string representation. + /// [int] and [double] values are multiplied by [factor] before returning. /// - /// - [name] - the name of value stored in AppSettings - /// - [factor] - returned value int or double will by multiplied by factor + /// Calls [onError] if getting value from [AppSettings] will fail. const Setting( String name, { double factor = 1.0, - }) : - _name = name, - _factor = factor; + dynamic Function(Failure err)? onError, + }) : _name = name, + _factor = factor, + _onError = onError; /// - /// + /// Returns [Setting] new instance containing a [value] const factory Setting.from(dynamic value) = _SettingValue; - /// - /// Returns setting value in int represantation + /// + /// Returns setting value in [int] representation int get toInt { - final value = AppSettings.getSetting(_name); + final value = AppSettings.getSetting(_name, onError: _onError); if (value is int) { return _factor == 1 ? value : (value * _factor).toInt(); } return (double.parse('$value') * _factor).toInt(); } - /// - /// Returns setting value in double represantation + /// + /// Returns setting value in [double] representation double get toDouble { - final value = AppSettings.getSetting(_name); + final value = AppSettings.getSetting(_name, onError: _onError); if (value is double) { return value * _factor; } return double.parse('$value') * _factor; } - /// - /// Returns setting value in string represantation + /// + /// Updates app setting to new [value] and save it asynchronously to file. + /// + /// Calls [onSuccess] or [onError] when setting will be updated + /// and saved successfully or with error. + Future update( + dynamic value, { + void Function(Failure error)? onError, + void Function()? onSuccess, + }) async { + await AppSettings.setSetting( + _name, + value, + onError: onError, + onSuccess: onSuccess, + ); + } + /// + /// Returns setting value in [String] representation @override - String toString() => '${AppSettings.getSetting(_name)}'; + String toString() => '${AppSettings.getSetting(_name, onError: _onError)}'; } - - +/// +/// class _SettingValue implements Setting { final dynamic _value; // @@ -55,6 +79,9 @@ class _SettingValue implements Setting { String get _name => throw UnimplementedError(); // @override + dynamic Function(Failure err)? get _onError => throw UnimplementedError(); + // + @override double get toDouble { final value = _value; if (value is double) { @@ -73,5 +100,14 @@ class _SettingValue implements Setting { } // @override + Future update( + dynamic value, { + void Function(Failure error)? onError, + void Function()? onSuccess, + }) async { + onError?.call(Failure('$runtimeType.update | Cannot update setting created during app runtime')); + } + // + @override String toString() => '$_value'; -} \ No newline at end of file +} diff --git a/lib/src/core/entities/alarm_colors.dart b/lib/src/core/entities/alarm_colors.dart deleted file mode 100644 index 700fac5..0000000 --- a/lib/src/core/entities/alarm_colors.dart +++ /dev/null @@ -1,18 +0,0 @@ -import 'package:flutter/material.dart'; -/// -class AlarmColors { - final Color class1; - final Color class2; - final Color class3; - final Color class4; - final Color class5; - final Color class6; - const AlarmColors({ - required this.class1, - required this.class2, - required this.class3, - required this.class4, - required this.class5, - required this.class6, - }); -} \ No newline at end of file diff --git a/lib/src/core/entities/ds_data_class.dart b/lib/src/core/entities/ds_data_class.dart index 844f8ba..11917c8 100644 --- a/lib/src/core/entities/ds_data_class.dart +++ b/lib/src/core/entities/ds_data_class.dart @@ -33,9 +33,8 @@ enum DsDataClass { factory DsDataClass.fromString(String value) { final dataClass = _valueMapping[value]; if (dataClass == null) { - throw Failure.connection( - message: 'Ошибка в методе $DsDataClass.fromString: неизвестный класс комманды "$value"', - stackTrace: StackTrace.current, + throw Failure( + 'Ошибка в методе $DsDataClass.fromString: неизвестный класс комманды "$value"', ); } return dataClass; diff --git a/lib/src/core/entities/ds_data_point.dart b/lib/src/core/entities/ds_data_point.dart index 57b33c6..748facf 100644 --- a/lib/src/core/entities/ds_data_point.dart +++ b/lib/src/core/entities/ds_data_point.dart @@ -76,11 +76,7 @@ class DsDataPoint implements IDataPoint { DsCot.reqCon || DsCot.actCon => Ok(this), DsCot.reqErr || DsCot.actErr => Err( - Failure( - message: value, - stackTrace: - StackTrace.current, - ), + Failure(value), ) }; } \ No newline at end of file diff --git a/lib/src/core/entities/ds_data_type.dart b/lib/src/core/entities/ds_data_type.dart index eed6604..e069208 100644 --- a/lib/src/core/entities/ds_data_type.dart +++ b/lib/src/core/entities/ds_data_type.dart @@ -41,9 +41,8 @@ enum DsDataType { final lvalue = value.toLowerCase(); final dataType = _valueMapping[lvalue]; if (dataType == null) { - throw Failure.connection( - message: 'Ошибка в методе $DsDataType._extract: неизвестный тип данных $value', - stackTrace: StackTrace.current, + throw Failure( + 'Ошибка в методе $DsDataType._extract: неизвестный тип данных $value', ); } return dataType; diff --git a/lib/src/core/entities/ds_point_name.dart b/lib/src/core/entities/ds_point_name.dart index 4ec824d..0bcc9e3 100644 --- a/lib/src/core/entities/ds_point_name.dart +++ b/lib/src/core/entities/ds_point_name.dart @@ -24,16 +24,14 @@ class DsPointName { } } throw Failure( - message: 'Ошибка в методе $DsPointName._findLastSeparatorIndex: неверный формат пути: "$source"', - stackTrace: StackTrace.current, + 'Ошибка в методе $DsPointName._findLastSeparatorIndex: неверный формат пути: "$source"', ); } /// static bool _validatePath(String source) { if (source[0] != '/') { throw Failure( - message: 'Ошибка в методе $DsPointName._validatePath: неверный формат пути: "$source"\n\tДолжен начинаться с "/"', - stackTrace: StackTrace.current, + 'Ошибка в методе $DsPointName._validatePath: неверный формат пути: "$source"\n\tДолжен начинаться с "/"', ); } return true; diff --git a/lib/src/core/entities/ds_status.dart b/lib/src/core/entities/ds_status.dart index 154b7ae..951305b 100644 --- a/lib/src/core/entities/ds_status.dart +++ b/lib/src/core/entities/ds_status.dart @@ -19,10 +19,8 @@ enum DsStatus { if (parsedValue != null) { return DsStatus.fromValue(parsedValue); } else { - throw Failure.connection( - message: - 'Ошибка в методе $DsStatus.fromString: int.parse.error значение: "$rawValue"', - stackTrace: StackTrace.current, + throw Failure( + 'Ошибка в методе $DsStatus.fromString: int.parse.error значение: "$rawValue"', ); } } @@ -30,10 +28,8 @@ enum DsStatus { factory DsStatus.fromValue(int code) { final status = _valueMapping[code]; if (status == null) { - throw Failure.connection( - message: - 'Ошибка в методе $DsStatus.fromValue: неизвестный статус "$code"', - stackTrace: StackTrace.current, + throw Failure( + 'Ошибка в методе $DsStatus.fromValue: неизвестный статус "$code"', ); } return status; diff --git a/lib/src/core/entities/ds_timestamp.dart b/lib/src/core/entities/ds_timestamp.dart index 232b18c..105f7d8 100644 --- a/lib/src/core/entities/ds_timestamp.dart +++ b/lib/src/core/entities/ds_timestamp.dart @@ -28,9 +28,8 @@ class DsTimeStamp { if (dateTime != null) { return DsTimeStamp(dateTime: dateTime); } else { - throw Failure.convertion( - message: 'Ошибка в методе $DsTimeStamp.parse: Недопустимый формат метки времени в:\n\t$value', - stackTrace: StackTrace.current, + throw Failure( + 'Ошибка в методе $DsTimeStamp.parse: Недопустимый формат метки времени в:\n\t$value', ); } } diff --git a/lib/src/core/entities/state_colors.dart b/lib/src/core/entities/state_colors.dart deleted file mode 100644 index 37bfda7..0000000 --- a/lib/src/core/entities/state_colors.dart +++ /dev/null @@ -1,28 +0,0 @@ -import 'package:flutter/material.dart'; -/// -class StateColors { - final Color error; - final Color alarm; - final Color obsolete; - final Color invalid; - final Color timeInvalid; - final Color lowLevel; - final Color alarmLowLevel; - final Color highLevel; - final Color alarmHighLevel; - final Color off; - final Color on; - const StateColors({ - required this.error, - required this.alarm, - required this.obsolete, - required this.invalid, - required this.timeInvalid, - required this.lowLevel, - required this.alarmLowLevel, - required this.highLevel, - required this.alarmHighLevel, - required this.off, - required this.on, - }); -} \ No newline at end of file diff --git a/lib/src/core/error/failure.dart b/lib/src/core/error/failure.dart index 01109fe..5393abd 100644 --- a/lib/src/core/error/failure.dart +++ b/lib/src/core/error/failure.dart @@ -1,66 +1,33 @@ -import 'package:hmi_core/src/core/log/log.dart'; - -class Failure { - static const _log = Log('Failure'); - final T message; /// /// Ganeral Failures - Failure({ - required this.message, - required StackTrace stackTrace, - }) { - _log.warning(message, this, stackTrace); +/// - `Failure('Me.area | Error message')` - error happens locally +/// - `Failure.pass('Me.area |', error)` - error in some dependency returned from Me.area +/// - `Failure.pass('Me.area | message', error)` - error in some dependency returned from Me.area with additional message +class Failure { + // static const _log = Log('Failure'); + final T message; + final dynamic _child; + /// + /// Ganeral Failures + Failure(this.message) : _child = null; + /// + /// Ganeral Failures passing incoming error with message + Failure.pass(this.message, dynamic err) : _child = err; + /// + /// Converts all children errors into single string + String _join(int depth) { + if (_child != null) { + final tab = List.filled(depth, '\t').join(); + if (_child is Failure) { + return '$message |\n$tab${_child?._join(depth + 1)}'; + } + return '$message |\n$tab$_child'; + } + return ''; } // - // dataSource failure - factory Failure.dataSource({ - required T message, - required StackTrace stackTrace, - }) => Failure(message: message, stackTrace: stackTrace); - // - // dataObject failure - factory Failure.dataObject({ - required T message, - required StackTrace stackTrace, - }) => Failure(message: message, stackTrace: stackTrace); - // - // dataCollection failure - factory Failure.dataCollection({ - required T message, - required StackTrace stackTrace, - }) => Failure(message: message, stackTrace: stackTrace); - // - // auth failure - factory Failure.auth({ - required T message, - required StackTrace stackTrace, - }) => Failure(message: message, stackTrace: stackTrace); - // - // convertion failure - factory Failure.convertion({ - required T message, - required StackTrace stackTrace, - }) => Failure(message: message, stackTrace: stackTrace); - // - // Connection failure - factory Failure.connection({ - required T message, - required StackTrace stackTrace, - }) => Failure(message: message, stackTrace: stackTrace); - // Translation failure - factory Failure.translation({ - required T message, - required StackTrace stackTrace, - }) => Failure(message: message, stackTrace: stackTrace); - // - // Unexpected failure - factory Failure.unexpected({ - required T message, - required StackTrace stackTrace, - }) => Failure(message: message, stackTrace: stackTrace); - @override String toString() { - return message.toString(); + return '$message${_join(1)}'; } } diff --git a/lib/src/core/json/json_list.dart b/lib/src/core/json/json_list.dart index 55797b4..a23de82 100644 --- a/lib/src/core/json/json_list.dart +++ b/lib/src/core/json/json_list.dart @@ -1,38 +1,70 @@ import 'dart:async'; import 'dart:convert'; -import 'package:hmi_core/src/core/error/failure.dart'; import 'package:hmi_core/src/core/log/log.dart'; -import 'package:hmi_core/src/core/result/result.dart'; import 'package:hmi_core/src/core/text_file.dart'; +import 'package:hmi_core/src/core/error/failure.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +import 'package:hmi_core/src/core/result/result_transform_extension.dart'; +import 'package:hmi_core/src/core/result/result_boolean_operations_extension.dart'; /// +/// Decode json string into [List]. class JsonList { - static const _log = Log('JsonList'); - final FutureOr> _content; - const JsonList._(FutureOr> content) : _content = content; - JsonList.fromString(String content) : this._(Ok(content)); - JsonList.fromTextFile(TextFile textFile) : this._(textFile.content); + static const _log = Log('JsonList '); + final FutureOr>> _contents; + const JsonList._(FutureOr>> contents) + : _contents = contents; + /// + /// Creates empty [JsonList]. + const JsonList.empty() : this._(const [Ok('')]); + /// + /// Creates [JsonList] that parses itself from [text] with json. + JsonList.fromString(String text) + : this._( + [Ok(text)], + ); + /// + /// Creates [JsonList] that parses itself from json stored in [textFile]. + JsonList.fromTextFile(TextFile textFile) + : this._( + textFile.content.then((content) => [content]), + ); + /// + /// Creates [JsonList] that parses itself from json stored in [textFiles]. + JsonList.fromTextFiles(List textFiles) + : this._(Future.wait( + textFiles.map((textFile) => textFile.content), + )); /// + /// Decodes json and returns [Ok] with decoded [List] + /// if successful, and [Err] otherwise. Future>> get decoded async { - const failureMessage = 'Failed to parse json list from file.'; - switch(await _content) { - case Ok(:final value): - try { - final decodedJson = const JsonCodec().decode(value) as List; - return Ok( - decodedJson.cast(), - ); - } catch(error) { - _log.warning(failureMessage); - return Err( - Failure( - message: error.toString(), - stackTrace: StackTrace.current, - ), - ); - } - case Err(:final error): - _log.warning(failureMessage); - return Err(error); - } + final contentsResults = await _contents; + final contentsResult = contentsResults.fold>>( + Ok(List.from([])), + (contentsResult, contentResult) => contentsResult.andThen( + (contents) => contentResult.andThen( + (content) => Ok(contents..add(content)), + ), + ), + ); + return contentsResult + .andThen( + (contents) => contents.fold>>( + Ok(List.from([])), + (listResult, content) => listResult.andThen((list) { + try { + final decodedJson = const JsonCodec().decode(content) as List; + return Ok(list..addAll(decodedJson.cast())); + } catch (error, _) { + return Err(Failure('$runtimeType.get | $error')); + } + }), + ), + ) + .inspectErr( + (error) => _log.warning( + 'Failed to parse list from json, ${error.message}', + ), + ); } -} \ No newline at end of file +} diff --git a/lib/src/core/json/json_map.dart b/lib/src/core/json/json_map.dart index a01e1c1..cfb870f 100644 --- a/lib/src/core/json/json_map.dart +++ b/lib/src/core/json/json_map.dart @@ -1,38 +1,70 @@ import 'dart:async'; import 'dart:convert'; -import 'package:hmi_core/src/core/error/failure.dart'; import 'package:hmi_core/src/core/log/log.dart'; -import 'package:hmi_core/src/core/result/result.dart'; import 'package:hmi_core/src/core/text_file.dart'; +import 'package:hmi_core/src/core/error/failure.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +import 'package:hmi_core/src/core/result/result_transform_extension.dart'; +import 'package:hmi_core/src/core/result/result_boolean_operations_extension.dart'; /// +/// Decode json string into [Map]. class JsonMap { - static const _log = Log('JsonMap'); - final FutureOr> _content; - const JsonMap._(FutureOr> content) : _content = content; - JsonMap.fromString(String content) : this._(Ok(content)); - JsonMap.fromTextFile(TextFile textFile) : this._(textFile.content); + static const _log = Log('JsonMap '); + final FutureOr>> _contents; + const JsonMap._(FutureOr>> contents) + : _contents = contents; + /// + /// Creates empty [JsonMap]. + const JsonMap.empty() : this._(const [Ok('')]); + /// + /// Creates [JsonMap] that parses itself from [text] with json. + JsonMap.fromString(String text) + : this._( + [Ok(text)], + ); + /// + /// Creates [JsonMap] that parses itself from json stored in [textFile]. + JsonMap.fromTextFile(TextFile textFile) + : this._( + textFile.content.then((content) => [content]), + ); + /// + /// Creates [JsonMap] that parses itself from json stored in [textFiles]. + JsonMap.fromTextFiles(List textFiles) + : this._(Future.wait( + textFiles.map((textFile) => textFile.content), + )); /// + /// Decodes json and returns [Ok] with decoded [Map] + /// if successful, and [Err] otherwise. Future>> get decoded async { - const failureMessage = 'Failed to parse json map from file.'; - switch(await _content) { - case Ok(:final value): - try { - final decodedJson = const JsonCodec().decode(value) as Map; - return Ok( - decodedJson.cast(), - ); - } catch(error) { - _log.warning(failureMessage); - return Err( - Failure( - message: error.toString(), - stackTrace: StackTrace.current, - ), - ); - } - case Err(:final error): - _log.warning(failureMessage); - return Err(error); - } + final contentsResults = await _contents; + final contentsResult = contentsResults.fold>>( + Ok(List.from([])), + (contentsResult, contentResult) => contentsResult.andThen( + (contents) => contentResult.andThen( + (content) => Ok(contents..add(content)), + ), + ), + ); + return contentsResult + .andThen( + (contents) => contents.fold>>( + Ok(Map.from({})), + (mapResult, content) => mapResult.andThen((map) { + try { + final decodedJson = const JsonCodec().decode(content) as Map; + return Ok(map..addAll(decodedJson.cast())); + } catch (error, _) { + return Err(Failure('$runtimeType.get | $error')); + } + }), + ), + ) + .inspectErr( + (error) => _log.warning( + 'Failed to parse map from json, ${error.message}', + ), + ); } -} \ No newline at end of file +} diff --git a/lib/src/core/log/console_colors.dart b/lib/src/core/log/console_colors.dart index f8d3139..da46336 100644 --- a/lib/src/core/log/console_colors.dart +++ b/lib/src/core/log/console_colors.dart @@ -1,44 +1,44 @@ /// /// Colors used to customize console output class ConsoleColors { - static const reset = '\x1b[0m'; // reset - static const bright = '\x1b[1m'; + static const reset = '\x1b[0m'; // reset + static const bright = '\x1b[1m'; // - static const dim = '\x1b[2m'; + static const dim = '\x1b[2m'; static const underscore = '\x1b[4m'; - static const blink = '\x1b[5m'; - static const reverse = '\x1b[7m'; - static const hidden = '\x1b[8m'; + static const blink = '\x1b[5m'; + static const reverse = '\x1b[7m'; + static const hidden = '\x1b[8m'; // // Regular foreground - static const fgBlack = '\x1b[0;30m'; // Black - static const fgRed = '\x1b[0;31m'; // Red - static const fgGreen = '\x1b[0;32m'; // Green - static const fgYellow = '\x1b[0;33m'; // Yellow - static const fgBlue = '\x1b[0;34m'; // Blue - static const fgPurple = '\x1b[0;35m'; // Purple - static const fgCyan = '\x1b[0;36m'; // Cyan - static const fgWhite = '\x1b[0;37m'; // White - static const fgGray = '\x1b[90m'; + static const fgBlack = '\x1b[30m'; // Black + static const fgRed = '\x1b[31m'; // Red + static const fgGreen = '\x1b[32m'; // Green + static const fgYellow = '\x1b[33m'; // Yellow + static const fgBlue = '\x1b[34m'; // Blue + static const fgPurple = '\x1b[35m'; // Purple + static const fgCyan = '\x1b[36m'; // Cyan + static const fgWhite = '\x1b[37m'; // White + static const fgGray = '\x1b[90m'; // // Bold - static const fgBoldBlack = '\x1b[1;30m'; // Black - static const fgBoldRed = '\x1b[1;31m'; // Red - static const fgBoldGreen = '\x1b[1;32m'; // Green - static const fgBoldYellow = '\x1b[1;33m'; // Yellow - static const fgBoldBlue = '\x1b[1;34m'; // Blue - static const fgBoldPurple = '\x1b[1;35m'; // Purple - static const fgBoldCyan = '\x1b[1;36m'; // Cyan - static const fgBoldWhite = '\x1b[1;37m'; // White + static const fgBoldBlack = '\x1b[1;30m'; // Bold Black + static const fgBoldRed = '\x1b[1;31m'; // Bold Red + static const fgBoldGreen = '\x1b[1;32m'; // Bold Green + static const fgBoldYellow = '\x1b[1;33m'; // Bold Yellow + static const fgBoldBlue = '\x1b[1;34m'; // Bold Blue + static const fgBoldPurple = '\x1b[1;35m'; // Bold Purple + static const fgBoldCyan = '\x1b[1;36m'; // Bold Cyan + static const fgBoldWhite = '\x1b[1;37m'; // Bold White // // Regular background - static const bgBlack = '\x1b[40m'; - static const bgRed = '\x1b[41m'; - static const bgGreen = '\x1b[42m'; - static const bgYellow = '\x1b[43m'; - static const bgBlue = '\x1b[44m'; - static const bgMagenta = '\x1b[45m'; - static const bgCyan = '\x1b[46m'; - static const bgWhite = '\x1b[47m'; - static const bgGray = '\x1b[100m'; + static const bgBlack = '\x1b[40m'; + static const bgRed = '\x1b[41m'; + static const bgGreen = '\x1b[42m'; + static const bgYellow = '\x1b[43m'; + static const bgBlue = '\x1b[44m'; + static const bgMagenta = '\x1b[45m'; + static const bgCyan = '\x1b[46m'; + static const bgWhite = '\x1b[47m'; + static const bgGray = '\x1b[100m'; } diff --git a/lib/src/core/log/log.dart b/lib/src/core/log/log.dart index 71492c4..a317413 100644 --- a/lib/src/core/log/log.dart +++ b/lib/src/core/log/log.dart @@ -3,7 +3,7 @@ import 'package:hmi_core/src/core/log/log_level.dart'; import 'package:logging/logging.dart'; export 'package:logging/logging.dart' hide Logger, Level; - +/// /// Use a [Log] to log debug messages. /// - First call initialize with optional root loging Level, default LogLevel.all /// - [Log]s are named using a hierarchical dot-separated name convention. @@ -18,21 +18,17 @@ class Log { hierarchicalLoggingEnabled = true; Logger.root.level = level ?? LogLevel.all; // defaults to Level.INFO Logger.root.onRecord.listen((record) { - if (record.level == LogLevel.all) { - _logColored(ConsoleColors.fgGray, '${record.time} | ${record.level.name} | ${record.loggerName}: ${record.message}'); - } else if (record.level == LogLevel.debug) { - _logColored(ConsoleColors.fgBlue, '${record.time} | ${record.level.name} | ${record.loggerName}: ${record.message}'); - } else if (record.level == LogLevel.config) { - _logColored(ConsoleColors.fgPurple, '${record.time} | ${record.level.name} | ${record.loggerName}: ${record.message}'); - } else if (record.level == LogLevel.info) { - _logColored(ConsoleColors.fgGray, '${record.time} | ${record.level.name} | ${record.loggerName}: ${record.message}'); - } else if (record.level == LogLevel.warning) { - _logColored(ConsoleColors.fgYellow, '${record.time} | ${record.level.name} | ${record.loggerName}: ${record.message}'); - } else if (record.level == LogLevel.error) { - _logColored(ConsoleColors.fgRed, '${record.time} | ${record.level.name} | ${record.loggerName}: ${record.message}'); - } else { - _logColored(ConsoleColors.fgGray, '${record.time} | ${record.level.name} | ${record.loggerName}: ${record.message}'); - } + final color = switch (record.level) { + LogLevel.all => ConsoleColors.fgGray, + LogLevel.trace => ConsoleColors.fgCyan, + LogLevel.debug => ConsoleColors.fgBlue, + LogLevel.config => ConsoleColors.fgPurple, + LogLevel.info => ConsoleColors.fgGray, + LogLevel.warning => ConsoleColors.fgYellow, + LogLevel.error => ConsoleColors.fgRed, + _ => ConsoleColors.fgGray, + }; + _logColored(color, '${record.time} | ${record.level.name} | ${record.loggerName}${record.message}'); }); } /// @@ -40,6 +36,13 @@ class Log { log(true, '$color$message${ConsoleColors.reset}'); } /// + /// Log message at level [LogLevel.trace]. + /// + /// See [log] for information on how non-String [message] arguments are + /// handled. + void trace(Object? message, [Object? error, StackTrace? stackTrace]) => + Logger(_name).log(LogLevel.trace, message, error, stackTrace); + /// /// Log message at level [LogLevel.debug]. /// /// See [log] for information on how non-String [message] arguments are @@ -61,6 +64,13 @@ class Log { void warning(Object? message, [Object? error, StackTrace? stackTrace]) => Logger(_name).log(LogLevel.warning, message, error, stackTrace); /// + /// Log message at level [LogLevel.warning]. + /// + /// See [log] for information on how non-String [message] arguments are + /// handled. + void warn(Object? message, [Object? error, StackTrace? stackTrace]) => + Logger(_name).log(LogLevel.warning, message, error, stackTrace); + /// /// Log message at level [LogLevel.error]. /// /// See [log] for information on how non-String [message] arguments are handled. diff --git a/lib/src/core/log/log_level.dart b/lib/src/core/log/log_level.dart index c30d338..e18e7d6 100644 --- a/lib/src/core/log/log_level.dart +++ b/lib/src/core/log/log_level.dart @@ -6,7 +6,7 @@ import 'package:logging/logging.dart'; /// follows (in descending order): /// - [LogLevel.off], /// - [LogLevel.error], [LogLevel.warning], -/// - [LogLevel.info], [LogLevel.config], [LogLevel.debug], +/// - [LogLevel.info], [LogLevel.config], [LogLevel.debug], [LogLevel.trace], /// - [LogLevel.all]. /// /// We recommend using one of the predefined logging levels. If you define your @@ -17,6 +17,9 @@ class LogLevel extends Level { /// Special key to turn on logging for all levels ([value] = 0). static const LogLevel all = LogLevel('ALL', 0); /// + /// Key for detailed tracing information ([value] = 400). + static const LogLevel trace = LogLevel('TRACE', 400);// Level.FINER; + /// /// Key for tracing information ([value] = 500). static const LogLevel debug = LogLevel('DEBUG', 500);// Level.FINE; /// @@ -61,6 +64,7 @@ class LogLevel extends Level { /// static const List levels = [ all, + trace, debug, config, info, diff --git a/lib/src/core/option/option_extract_extension.dart b/lib/src/core/option/option_extract_extension.dart new file mode 100644 index 0000000..9a9d7af --- /dev/null +++ b/lib/src/core/option/option_extract_extension.dart @@ -0,0 +1,205 @@ +import 'package:hmi_core/hmi_core_failure.dart'; +import 'package:hmi_core/hmi_core_option.dart'; +/// +/// Extracting contained values +extension OptionExtract on Option { + /// + /// Returns the contained [Some] value, consuming the `self` value. + /// + /// Because this function may panic, its use is generally discouraged. + /// Instead, prefer to use pattern matching and handle the [`None`] + /// case explicitly, or call [`unwrap_or`], [`unwrap_or_else`], or + /// [`unwrap_or_default`]. + /// + /// [`unwrap_or`]: Option::unwrap_or + /// [`unwrap_or_else`]: Option::unwrap_or_else + /// [`unwrap_or_default`]: Option::unwrap_or_default + /// + /// Throws an [Failure] if the self value equals [`None`]. + /// + /// # Examples + /// + /// ```dart + /// final Option x = Ok(2); + /// x.unwrap(); // 2 + /// ``` + /// + /// Should throw a Failure: + /// ```dart + /// final Result x = Err("emergency failure"); + /// // throw Failure with message: "Called unwrap() on Result with error: emergency failure" + /// x.unwrap(); + /// ``` + V unwrap() { + return switch (this) { + Some(:final V value) => value, + None() => throw Failure("$runtimeType.unwrap() | Called on None"), + }; + } + /// + /// Returns the contained [Some] value, consuming the `self` value. + /// + /// # Throws an error if the value is a [None] with a custom error message provided by + /// `msg`. + /// + /// # Examples + /// + /// Basic usage: + /// + /// ```dart + /// final Option x = Some(2); + /// x.expect("A custom message"); // 2 + /// ``` + /// + /// Should throw a Failure: + /// + /// ```dart + /// final Option x = None(); + /// // throw Failure with message: "Testing expect: emergency failure" + /// x.expect("Testing expect"); + /// ``` + V expect(String message) { + return switch (this) { + Some(:final V value) => value, + None() => throw Failure(message), + }; + } + + // /// + // /// # Examples + // /// + // /// ```dart + // /// final Option = None(); + // /// x.unwrapErr(); // "emergency failure" + // /// ``` + // /// + // /// Should throw a Failure: + // /// + // /// ```dart + // /// final Result x = Ok(2); + // /// // throw Failure with message: "Called unwrapErr() on Result with value: 2" + // /// x.unwrapErr(); + // /// ``` + // E unwrapErr() { + // return switch (this) { + // Ok(:final V value) => throw Failure( + // message: "Called unwrapErr() on Result with value: $value", + // stackTrace: StackTrace.current, + // ), + // Err(:final E error) => error, + // }; + // } + // /// + // /// Returns the contained [Err] error, consuming the `this` value. + // /// + // /// ### Throws an error + // /// + // /// Throws an [Failure] if the value is an [Ok], with a failure message including the + // /// passed message, and the content of the [Ok]. + // /// + // /// + // /// ### Examples + // /// + // /// Basic usage: + // /// ```dart + // /// final Result x = Err("emergency failure"); + // /// x.expectErr("A custom message"); // "emergency failure" + // /// ``` + // /// + // /// Should throw a Failure: + // /// ```dart + // /// final Result x = Ok(2); + // /// // throw Failure with message: "Testing expectErr: 2" + // /// x.expectErr("Testing expectErr"); + // /// ``` + // E expectErr(String message) { + // return switch (this) { + // Ok(:final V value) => throw Failure( + // message: "$message: $value", + // stackTrace: StackTrace.current, + // ), + // Err(:final E error) => error, + // }; + // } + /// + /// Returns the contained [Some] value or a provided default. + /// + /// Arguments passed to `unwrap_or` are eagerly evaluated; if you are passing + /// the result of a function call, it is recommended to use [`unwrap_or_else`], + /// which is lazily evaluated. + /// + /// ### Examples + /// + /// ```dart + /// final defaultValue = 2; + /// final Option x = Some(9); + /// x.unwrapOr(defaultValue); // 9 + /// // + /// final Option y = None(); + /// y.unwrapOr(defaultValue); // 2 + /// ``` + V unwrapOr(V d) { + return switch (this) { + Some(:final V value) => value, + None() => d, + }; + } + /// + /// Returns the contained [`Some`] value or computes it from a closure. + /// + /// ### Examples + /// + /// ```dart + /// int onNone() => 'sNone'; + /// + /// final Option x = Some('isSome'); + /// x.unwrapOrElse(onNone); // 'isSome' + /// + /// final Option y = None(); + /// y.unwrapOrElse(onNone); // 'isNone' + /// ``` + V unwrapOrElse(V Function() d) { + return switch (this) { + Some(:final V value) => value, + None() => d(), + }; + } +} +// /// +// /// Extracting contained values for [Ok] +// extension ExtractOk on Result { +// /// +// /// Returns the contained [Ok] value, but never throw an error. +// /// +// /// It can be used as a maintainability safeguard that will fail +// /// to compile if the error type of the [Result] is later changed +// /// from [Never] to a type that can actually occur. +// /// +// /// ### Examples +// /// ```dart +// /// Result onlyGoodNews() => Ok("this is fine"); +// /// onlyGoodNews().intoOk(); // "this is fine" +// /// ``` +// V intoOk() { +// return (this as Ok).value; +// } +// } +// /// +// /// Extracting contained values for [Err] +// extension ExtractErr on Result { +// /// +// /// Returns the contained [Err] value, but never throw an error. +// /// +// /// It can be used as a maintainability safeguard that will fail +// /// to compile if the ok type of the [Result] is later changed +// /// from [Never] to a type that can actually occur. +// /// +// /// ### Examples +// /// ```dart +// /// Result onlyBadNews() => Err("Oops, it failed"); +// /// onlyBadNews().intoErr(); // "Oops, it failed" +// /// ``` +// E intoErr() { +// return (this as Err).error; +// } +// } diff --git a/lib/src/core/option/option_querying_extension.dart b/lib/src/core/option/option_querying_extension.dart new file mode 100644 index 0000000..2606091 --- /dev/null +++ b/lib/src/core/option/option_querying_extension.dart @@ -0,0 +1,39 @@ +import 'package:hmi_core/hmi_core_option.dart'; +/// +/// Querying the contained values +extension OptionQuerying on Option { + /// + /// Returns `true` if the option is [Some], and `false` otherwise. + /// + /// ### Examples + /// ```dart + /// final Option x = Some(-3); + /// x.isSome(); // true + /// + /// final Option y = None(); + /// y.isSome(); // false + /// ``` + bool isSome() { + return switch (this) { + Some() => true, + None() => false, + }; + } + /// + /// Returns `true` if the option is [None], and `false` otherwise. + /// + /// ### Examples + /// ```dart + /// final Option x = Some(-3); + /// x.isNone(); // false + /// + /// final Option y = None(); + /// y.isNone(); // true + /// ``` + bool isNone() { + return switch (this) { + Some() => false, + None() => true, + }; + } +} diff --git a/lib/src/core/option/option_transform_extension.dart b/lib/src/core/option/option_transform_extension.dart new file mode 100644 index 0000000..e210401 --- /dev/null +++ b/lib/src/core/option/option_transform_extension.dart @@ -0,0 +1,34 @@ +import 'package:hmi_core/hmi_core_option.dart'; +import 'package:hmi_core/hmi_core_result.dart'; +/// +/// Transforming contained values +extension OptionTransform on Option { + /// + /// Transforms the `Option` into a [Result], + /// mapping [Some(v)] to [Ok(v)] and [None] to [Err(err)]. + /// + /// Arguments passed to `ok_or` are eagerly evaluated; if you are passing the + /// result of a function call, it is recommended to use [`ok_or_else`], which is + /// lazily evaluated. + /// + /// [`Ok(v)`]: Ok + /// [`Err(err)`]: Err + /// [`Some(v)`]: Some + /// [`ok_or_else`]: Option::ok_or_else + /// + /// # Examples + /// + /// ``` + /// let x = Some("foo"); + /// assert_eq!(x.ok_or(0), Ok("foo")); + /// + /// let x: Option<&str> = None; + /// assert_eq!(x.ok_or(0), Err(0)); + /// ``` + Result okOr(E err) { + return switch (this) { + Some(:final value) => Ok(value), + None() => Err(err), + }; + } +} diff --git a/lib/src/core/result/result_extract_extension.dart b/lib/src/core/result/result_extract_extension.dart index e3bc745..12812f7 100644 --- a/lib/src/core/result/result_extract_extension.dart +++ b/lib/src/core/result/result_extract_extension.dart @@ -34,8 +34,7 @@ extension ResultExtract on Result { return switch (this) { Ok(:final V value) => value, Err(:final E error) => throw Failure( - message: "Called unwrap() on Result with error: $error", - stackTrace: StackTrace.current, + 'Called unwrap() on Result with error: $error', ), }; } @@ -70,8 +69,7 @@ extension ResultExtract on Result { return switch (this) { Ok(:final V value) => value, Err(:final E error) => throw Failure( - message: "$message: $error", - stackTrace: StackTrace.current, + '$message: $error', ), }; } @@ -98,8 +96,7 @@ extension ResultExtract on Result { E unwrapErr() { return switch (this) { Ok(:final V value) => throw Failure( - message: "Called unwrapErr() on Result with value: $value", - stackTrace: StackTrace.current, + 'Called unwrapErr() on Result with value: $value', ), Err(:final E error) => error, }; @@ -130,8 +127,7 @@ extension ResultExtract on Result { E expectErr(String message) { return switch (this) { Ok(:final V value) => throw Failure( - message: "$message: $value", - stackTrace: StackTrace.current, + '$message: $value', ), Err(:final E error) => error, }; diff --git a/lib/src/core/text_file.dart b/lib/src/core/text_file.dart index bfbe959..d8694a8 100644 --- a/lib/src/core/text_file.dart +++ b/lib/src/core/text_file.dart @@ -37,10 +37,7 @@ class _PathTextFile implements TextFile { case false: _log.warning('Failed to read from file.'); return Err( - Failure( - message: 'File $_filePath does not exist.', - stackTrace: StackTrace.current, - ), + Failure('File $_filePath does not exist.'), ); } } @@ -61,10 +58,7 @@ class _AssetTextFile implements TextFile { .catchError((error) { _log.warning('Failed to read from asset.'); return Err( - Failure( - message: error.toString(), - stackTrace: StackTrace.current, - ), + Failure.pass('$runtimeType.get |', error), ); }); } diff --git a/lib/src/translate/localizations.dart b/lib/src/translate/localizations.dart index db858eb..34d51e8 100644 --- a/lib/src/translate/localizations.dart +++ b/lib/src/translate/localizations.dart @@ -280,4 +280,12 @@ class Localizations { } return translations[_appLang.index]; } + /// + /// Returns current App lang + AppLang get appLang => _appLang; + /// + /// Returns list of available languages + List get languages { + return AppLang.values; + } } diff --git a/pubspec.yaml b/pubspec.yaml index 99e9b7e..4c19029 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: hmi_core description: A Flutter core package of hmi_widgets package which is helps to visualize states, values, changes and other artefacts from technological processes. -version: 2.1.1 +version: 2.1.6 homepage: https://github.com/a-givertzman/hmi_core environment: diff --git a/test/unit/auth/user_password_test.dart b/test/unit/auth/user_password_test.dart index a4463f3..158e21e 100644 --- a/test/unit/auth/user_password_test.dart +++ b/test/unit/auth/user_password_test.dart @@ -1,13 +1,11 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hmi_core/hmi_core.dart'; - - void main() { group('UserPassword', () { Log.initialize(level: LogLevel.all); const log = Log('UserPassword'); AppSettings.initialize( - jsonMap: JsonMap.fromString('{"passwordKey": "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЁЯЧСМИТЬБЮйцукенгшщзхъфывапролджэёячсмитьбю"}'), + readOnly: JsonMap.fromString('{"passwordKey": "1234567890qwertyuiopasdfghjklzxcvbnmQWERTYUIOPASDFGHJKLZXCVBNMЙЦУКЕНГШЩЗХЪФЫВАПРОЛДЖЭЁЯЧСМИТЬБЮйцукенгшщзхъфывапролджэёячсмитьбю"}'), ); test('value', () async { const rawPassValue = '123qwe'; diff --git a/test/unit/core/entities/alarm_colors/alarm_colors_test.dart b/test/unit/core/entities/alarm_colors/alarm_colors_test.dart deleted file mode 100644 index 8c82ad0..0000000 --- a/test/unit/core/entities/alarm_colors/alarm_colors_test.dart +++ /dev/null @@ -1,31 +0,0 @@ -import 'package:flutter/rendering.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hmi_core/hmi_core.dart'; - -void main() { - test('AlarmColors test', () { - final colors = { - 1: const Color(0xAA000001), - 2: const Color(0xAA000002), - 3: const Color(0xAA000003), - 4: const Color(0xAA000004), - 5: const Color(0xAA000005), - 6: const Color(0xAA000006), - }; - final alarmColors = AlarmColors( - class1: colors[1]!, - class2: colors[2]!, - class3: colors[3]!, - class4: colors[4]!, - class5: colors[5]!, - class6: colors[6]!, - ); - expect(alarmColors, isA()); - expect(alarmColors.class1, colors[1]!); - expect(alarmColors.class2, colors[2]!); - expect(alarmColors.class3, colors[3]!); - expect(alarmColors.class4, colors[4]!); - expect(alarmColors.class5, colors[5]!); - expect(alarmColors.class6, colors[6]!); - }); -} \ No newline at end of file diff --git a/test/unit/core/entities/app_settings/app_settings_initialize_test.dart b/test/unit/core/entities/app_settings/app_settings_initialize_test.dart index be8d566..b02a553 100644 --- a/test/unit/core/entities/app_settings/app_settings_initialize_test.dart +++ b/test/unit/core/entities/app_settings/app_settings_initialize_test.dart @@ -2,7 +2,6 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hmi_core/hmi_core.dart'; import 'fake_text_file.dart'; import 'settings_data.dart'; - void main() { Log.initialize(); group('AppUiSettings initialize', () { @@ -11,13 +10,14 @@ void main() { final textFile = settings['text_file'] as String; final setSettings = settings['set_settings'] as Map; await AppSettings.initialize( - jsonMap: JsonMap.fromTextFile( + readOnly: JsonMap.fromTextFile( FakeTextFile(textFile), ), ); - expect(AppSettings.getSetting('test_setting_1'), setSettings['test_setting_1']); - expect(AppSettings.getSetting('test_setting_2'), setSettings['test_setting_2']); - expect(AppSettings.getSetting('test_setting_3'), setSettings['test_setting_3']); + expect(AppSettings.getSetting('test_setting_1', onError: ((err) => null)), setSettings['test_setting_1']); + expect(AppSettings.getSetting('test_setting_2', onError: ((err) => null)), setSettings['test_setting_2']); + expect(AppSettings.getSetting('test_setting_3', onError: ((err) => null)), setSettings['test_setting_3']); + expect(AppSettings.getSetting('1234', onError: ((err) => 1234)), 1234); } }); // test('throws with valid json format and invalid data', () async { @@ -37,7 +37,7 @@ void main() { for (final invalidJson in invalidJsons) { final textFile = invalidJson['text_file'] as String; expectLater(AppSettings.initialize( - jsonMap: JsonMap.fromTextFile( + readOnly: JsonMap.fromTextFile( FakeTextFile(textFile), ), ), diff --git a/test/unit/core/entities/app_settings/app_settings_initialize_writable_test.dart b/test/unit/core/entities/app_settings/app_settings_initialize_writable_test.dart new file mode 100644 index 0000000..07b5574 --- /dev/null +++ b/test/unit/core/entities/app_settings/app_settings_initialize_writable_test.dart @@ -0,0 +1,234 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/hmi_core_app_settings.dart'; +import 'package:hmi_core/src/core/json/json_map.dart'; +import 'package:hmi_core/src/core/log/log.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +import 'package:hmi_core/src/core/error/failure.dart'; +import 'fake_text_file.dart'; +/// +/// Fake implementation of [JsonMap] for tests +class FakeJsonMap implements JsonMap { + final ResultF> _map; + /// + /// Creates [FakeJsonMap] with a given [map]. + const FakeJsonMap(ResultF> map) : _map = map; + // + @override + Future>> get decoded async { + return _map; + } +} +// +void main() { + Log.initialize(); + final validReadOnlyMaps = [ + { + 'test_setting_1': 0, + 'test_setting_2': 27, + 'test_setting_3': -123, + }, // int + { + 'test_setting_1': 0.0, + 'test_setting_2': 27.0, + 'test_setting_3': -123.0, + }, // double + { + 'test_setting_1': '0', + 'test_setting_2': '27', + 'test_setting_3': '-123', + }, // string + { + 'test_setting_1': true, + 'test_setting_2': false, + 'test_setting_3': true, + }, // bool + ]; + final validWritableMaps = [ + { + 'test_writable_setting_1': 0, + 'test_writable_setting_2': 27, + 'test_writable_setting_3': -123, + }, // int + { + 'test_writable_setting_1': 0.0, + 'test_writable_setting_2': 27.0, + 'test_writable_setting_3': -123.0, + }, // double + { + 'test_writable_setting_1': '0', + 'test_writable_setting_2': '27', + 'test_writable_setting_3': '-123', + }, // string + { + 'test_writable_setting_1': true, + 'test_writable_setting_2': false, + 'test_writable_setting_3': true, + }, // bool + ]; + group('AppSettings with writable initialize', () { + test('sets data normally with valid writable json map', () async { + for (final map in validWritableMaps) { + await AppSettings.initialize(writable: FakeJsonMap(Ok(map))); + expect( + AppSettings.getSetting('test_writable_setting_1', onError: ((err) => null)), + map['test_writable_setting_1'], + ); + expect( + AppSettings.getSetting('test_writable_setting_2', onError: ((err) => null)), + map['test_writable_setting_2'], + ); + expect( + AppSettings.getSetting('test_writable_setting_3', onError: ((err) => null)), + map['test_writable_setting_3'], + ); + } + }); + test('sets data normally with valid readOnly and writable json maps', () async { + // + for (final readOnly in validReadOnlyMaps) { + for (final writable in validWritableMaps) { + await AppSettings.initialize( + readOnly: FakeJsonMap(Ok(readOnly)), + writable: FakeJsonMap(Ok(writable)), + ); + expect( + AppSettings.getSetting('test_setting_1', onError: ((err) => null)), + readOnly['test_setting_1'], + ); + expect( + AppSettings.getSetting('test_setting_2', onError: ((err) => null)), + readOnly['test_setting_2'], + ); + expect( + AppSettings.getSetting('test_setting_3', onError: ((err) => null)), + readOnly['test_setting_3'], + ); + expect( + AppSettings.getSetting('test_writable_setting_1', onError: ((err) => null)), + writable['test_writable_setting_1'], + ); + expect( + AppSettings.getSetting('test_writable_setting_2', onError: ((err) => null)), + writable['test_writable_setting_2'], + ); + expect( + AppSettings.getSetting('test_writable_setting_3', onError: ((err) => null)), + writable['test_writable_setting_3'], + ); + } + } + }); + test('writable settings overwrite readOnly settings', () async { + await AppSettings.initialize( + readOnly: const FakeJsonMap(Ok({ + 'test_overwrite_setting_1': 0, + 'test_overwrite_setting_2': 27, + 'test_overwrite_setting_3': -123, + })), + writable: const FakeJsonMap(Ok({ + 'test_overwrite_setting_1': -123, + 'test_overwrite_setting_2': 0, + 'test_overwrite_setting_3': 27, + })), + ); + expect(AppSettings.getSetting('test_overwrite_setting_1'), -123); + expect(AppSettings.getSetting('test_overwrite_setting_2'), 0); + expect(AppSettings.getSetting('test_overwrite_setting_3'), 27); + }); + test('restore writable setting from store file', () async { + for (final map in validWritableMaps) { + final storeFile = FakeTextFile( + '{"test_writable_setting_1":"rewritten","test_writable_setting_2":"rewritten","test_writable_setting_3":"rewritten"}', + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: storeFile, + ); + expect(AppSettings.getSetting('test_writable_setting_1'), "rewritten"); + expect(AppSettings.getSetting('test_writable_setting_2'), "rewritten"); + expect(AppSettings.getSetting('test_writable_setting_3'), "rewritten"); + } + }); + test('ignore read only settings in store file', () async { + for (final map in validReadOnlyMaps) { + final storeFile = FakeTextFile( + '{"test_setting_1":"rewritten","test_setting_2":"rewritten","test_setting_3":"rewritten"}', + ); + await AppSettings.initialize( + readOnly: FakeJsonMap(Ok(map)), + store: storeFile, + ); + expect(AppSettings.getSetting('test_setting_1'), map['test_setting_1']); + expect(AppSettings.getSetting('test_setting_2'), map['test_setting_2']); + expect(AppSettings.getSetting('test_setting_3'), map['test_setting_3']); + } + }); + test('ignore not found settings in store file', () async { + final storeFile = FakeTextFile( + '{"test_not_found_setting_1":"rewritten","test_not_found_setting_2":"rewritten","test_not_found_setting_3":"rewritten"}', + ); + await AppSettings.initialize(store: storeFile); + expect( + AppSettings.getSetting('test_not_found_setting_1', onError: ((err) => null)), + null, + ); + expect( + AppSettings.getSetting('test_not_found_setting_2', onError: ((err) => null)), + null, + ); + expect( + AppSettings.getSetting('test_not_found_setting_3', onError: ((err) => null)), + null, + ); + }); + test('getSetting onError passes new value case error', () async { + final notFoundMaps = [ + { + 'key': 'not_found_1', + 'map_value': 0, + }, // int + { + 'key': 'not_found_2', + 'map_value': 0.0, + }, // int + { + 'key': 'not_found_3', + 'map_value': false, + }, // bool + { + 'key': 'not_found_4', + 'map_value': '0', + }, // string + { + 'key': 'not_found_5', + 'map_value': null, + }, // null + ]; + for (final map in notFoundMaps) { + await AppSettings.initialize(); + final key = map['key'] as String; + final mapValue = map['map_value'] as dynamic; + expect( + AppSettings.getSetting(key, onError: ((err) => mapValue)), + mapValue, + ); + } + }); + test('does not throw error with invalid json map', () async { + final mapWithError = FakeJsonMap( + Err(Failure( + message: 'invalid json map', + stackTrace: StackTrace.current, + )), + ); + await expectLater( + AppSettings.initialize(writable: mapWithError), + completes, + ); + await expectLater( + AppSettings.initialize(readOnly: mapWithError, writable: mapWithError), + completes, + ); + }); + }); +} diff --git a/test/unit/core/entities/app_settings/app_settings_set_setting_test.dart b/test/unit/core/entities/app_settings/app_settings_set_setting_test.dart new file mode 100644 index 0000000..d2a0dda --- /dev/null +++ b/test/unit/core/entities/app_settings/app_settings_set_setting_test.dart @@ -0,0 +1,223 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/src/app_settings/app_settings.dart'; +import 'package:hmi_core/src/core/json/json_map.dart'; +import 'package:hmi_core/src/core/log/log.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +import 'fake_text_file.dart'; +/// +/// Fake implementation of [JsonMap] for tests +class FakeJsonMap implements JsonMap { + final ResultF> _map; + /// + /// Creates [FakeJsonMap] with a given [map]. + const FakeJsonMap(ResultF> map) : _map = map; + // + @override + Future>> get decoded async { + return _map; + } +} +// +void main() { + Log.initialize(); + final testsData = [ + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":0}', + 'set_value': 0, + }, // same value + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":-27}', + 'set_value': -27, + }, // int + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":-27.0}', + 'set_value': -27.0, + }, // double + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":"abc"}', + 'set_value': 'abc', + }, // string + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":true}', + 'set_value': true, + }, // bool + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":null}', + 'set_value': null, + }, // null + ]; + group('AppSettings setSetting', () { + test('write setting to file', () async { + for (final data in testsData) { + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeJson = data['write_json'] as String; + String writeContent = ''; + final writeFile = FakeTextFile( + writeContent, + writeFuture: (value) async { + writeContent = value; + }, + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await AppSettings.setSetting(key, setValue); + expect(writeJson, writeContent); + } + }); + test('update setting value', () async { + for (final data in testsData) { + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.value(), + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await AppSettings.setSetting(key, setValue); + expect(AppSettings.getSetting(key), setValue); + } + }); + test('call onSuccess if setting saved successfully', () async { + for (final data in testsData) { + bool isSuccess = false; + bool isError = false; + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.value(), + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await AppSettings.setSetting( + key, + setValue, + onSuccess: () { + isSuccess = true; + }, + onError: (_) { + isError = true; + }, + ); + expect(isSuccess, isTrue); + expect(isError, isFalse); + } + }); + test('call onError if setting saved with error', () async { + for (final data in testsData) { + bool isSuccess = false; + bool isError = false; + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.error('write error'), + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await AppSettings.setSetting( + key, + setValue, + onSuccess: () { + isSuccess = true; + }, + onError: (_) { + isError = true; + }, + ); + expect(isSuccess, isFalse); + expect(isError, isTrue); + } + }); + test('call onError if setting is not found', () async { + bool isSuccess = false; + bool isError = false; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.error('write error'), + ); + await AppSettings.initialize( + store: writeFile, + ); + await AppSettings.setSetting( + 'test_not_found_setting', + 10, + onSuccess: () { + isSuccess = true; + }, + onError: (_) { + isError = true; + }, + ); + expect(isSuccess, isFalse); + expect(isError, isTrue); + }); + test('call onError if setting is not writable', () async { + bool isSuccess = false; + bool isError = false; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.error('write error'), + ); + await AppSettings.initialize( + readOnly: const FakeJsonMap(Ok({"test_not_write_setting": 0})), + store: writeFile, + ); + await AppSettings.setSetting( + 'test_not_write_setting', + 10, + onSuccess: () { + isSuccess = true; + }, + onError: (_) { + isError = true; + }, + ); + expect(isSuccess, isFalse); + expect(isError, isTrue); + }); + test('returns setting to previous value on error', () async { + for (final data in testsData) { + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.error('write error'), + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await AppSettings.setSetting(key, setValue); + expect(AppSettings.getSetting(key), map[key]); + } + }); + }); +} diff --git a/test/unit/core/entities/app_settings/fake_text_file.dart b/test/unit/core/entities/app_settings/fake_text_file.dart index 4955e07..ef92fae 100644 --- a/test/unit/core/entities/app_settings/fake_text_file.dart +++ b/test/unit/core/entities/app_settings/fake_text_file.dart @@ -2,13 +2,16 @@ import 'package:hmi_core/src/core/result/result.dart'; import 'package:hmi_core/src/core/text_file.dart'; /// class FakeTextFile implements TextFile { - final String text; - final Future? writeFuture; - const FakeTextFile(this.text, {this.writeFuture}); + String text; + final Future Function(String value)? writeFuture; + FakeTextFile(this.text, {this.writeFuture}); // @override Future> get content => Future.value(Ok(text)); // @override - Future write(String text) => writeFuture ?? Future(() {return;}); -} \ No newline at end of file + Future write(String text) async { + await writeFuture?.call(text); + this.text = text; + } +} diff --git a/test/unit/core/entities/app_settings/setting_from_test.dart b/test/unit/core/entities/app_settings/setting_from_test.dart index dd9c6b9..a913065 100644 --- a/test/unit/core/entities/app_settings/setting_from_test.dart +++ b/test/unit/core/entities/app_settings/setting_from_test.dart @@ -1,13 +1,12 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hmi_core/hmi_core.dart'; import 'package:hmi_core/hmi_core_app_settings.dart'; - void main() { group('Setting.from', () { Log.initialize(level: LogLevel.all); const log = Log('Setting'); const values = [12, -12, 0.44, -0.12, 123.32, -432.21]; - test('toInt', () async { + test('toInt', () { for (final value in values) { final setting = Setting.from(value); final vInt = setting.toInt; @@ -15,7 +14,7 @@ void main() { expect(vInt, value.toInt()); } }); - test('toDouble', () async { + test('toDouble', () { for (final value in values) { final setting = Setting.from(value); final vDauble = setting.toDouble; @@ -23,7 +22,7 @@ void main() { expect(vDauble, value); } }); - test('toString', () async { + test('toString', () { const values = [12, -12, 0.44, -0.12, 123.32, -432.21, 'aef323ewf']; for (final value in values) { final setting = Setting.from(value); @@ -32,5 +31,18 @@ void main() { expect(vString, '$value'); } }); + test('update', () async { + const values = [12, -12, 0.44, -0.12, 123.32, -432.21, 'aef323ewf']; + for (final value in values) { + final setting = Setting.from(value); + bool? isUpdateFailed; + await setting.update( + value, + onError: (_) => isUpdateFailed = true, + onSuccess: () => isUpdateFailed = false, + ); + expect(isUpdateFailed, isTrue); + } + }); }); -} \ No newline at end of file +} diff --git a/test/unit/core/entities/app_settings/setting_test.dart b/test/unit/core/entities/app_settings/setting_test.dart index de3e250..255a010 100644 --- a/test/unit/core/entities/app_settings/setting_test.dart +++ b/test/unit/core/entities/app_settings/setting_test.dart @@ -3,9 +3,7 @@ import 'package:hmi_core/hmi_core.dart'; import 'package:hmi_core/hmi_core_app_settings.dart'; import 'fake_text_file.dart'; import 'settings_data.dart'; - void main() { - Log.initialize(); group('Setting', () { Log.initialize(level: LogLevel.all); const log = Log('Setting'); @@ -14,7 +12,7 @@ void main() { final textFile = settings['text_file'] as String; final setSettings = settings['set_settings'] as Map; await AppSettings.initialize( - jsonMap: JsonMap.fromTextFile( + readOnly: JsonMap.fromTextFile( FakeTextFile(textFile), ), ); @@ -41,6 +39,35 @@ void main() { expect(const Setting('test_setting_3').toString(), setSettings['test_setting_3']!.toString()); } }); + test('key not found', () async { + for (final settings in validSettings) { + final textFile = settings['text_file'] as String; + final setSettings = settings['set_settings'] as Map; + await AppSettings.initialize( + readOnly: JsonMap.fromTextFile( + FakeTextFile(textFile), + ), + ); + for (final setting in setSettings.entries) { + final int testSetting = Setting(setting.key).toInt; + log.debug('as Int | ${setting.key}: $testSetting'); + } + for (final setting in setSettings.entries) { + final double testSetting = Setting(setting.key).toDouble; + log.debug('as Double | ${setting.key}: $testSetting'); + } + for (final setting in setSettings.entries) { + final String testSetting = Setting(setting.key).toString(); + log.debug('as String | ${setting.key}: $testSetting'); + } + expect(Setting('test_setting_1111', onError: (err) => 111,).toInt, 111); + expect(Setting('test_setting_1222', onError: (err) => -222,).toInt, -222); + expect(Setting('test_setting_1.10', onError: (err) => 1.10,).toDouble, 1.10); + expect(Setting('test_setting_2.22', onError: (err) => 2.22,).toDouble, 2.22); + expect(Setting('test_setting_1234', onError: (err) => '1234').toString(), '1234'); + expect(Setting('test_setting_2345', onError: (err) => '2345').toString(), '2345'); + } + }); test('valid data with factor', () async { const factors = [0.123, -0.123, 3.54, -5.12]; for (final factor in factors) { @@ -48,7 +75,7 @@ void main() { final textFile = settings['text_file'] as String; final setSettings = settings['set_settings'] as Map; await AppSettings.initialize( - jsonMap: JsonMap.fromTextFile( + readOnly: JsonMap.fromTextFile( FakeTextFile(textFile), ), ); @@ -76,7 +103,7 @@ void main() { for (final invalidJson in invalidJsons) { final textFile = invalidJson['text_file'] as String; expectLater(AppSettings.initialize( - jsonMap: JsonMap.fromTextFile( + readOnly: JsonMap.fromTextFile( FakeTextFile(textFile), ), ), diff --git a/test/unit/core/entities/app_settings/setting_update_test.dart b/test/unit/core/entities/app_settings/setting_update_test.dart new file mode 100644 index 0000000..7321d55 --- /dev/null +++ b/test/unit/core/entities/app_settings/setting_update_test.dart @@ -0,0 +1,220 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/src/app_settings/app_settings.dart'; +import 'package:hmi_core/src/app_settings/setting.dart'; +import 'package:hmi_core/src/core/json/json_map.dart'; +import 'package:hmi_core/src/core/log/log.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +import 'fake_text_file.dart'; +/// +/// Fake implementation of [JsonMap] for tests +class FakeJsonMap implements JsonMap { + final ResultF> _map; + /// + /// Creates [FakeJsonMap] with a given [map]. + const FakeJsonMap(ResultF> map) : _map = map; + // + @override + Future>> get decoded async { + return _map; + } +} +// +void main() { + Log.initialize(); + final testsData = [ + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":0}', + 'set_value': 0, + }, // same value + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":-27}', + 'set_value': -27, + }, // int + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":-27.0}', + 'set_value': -27.0, + }, // double + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":"abc"}', + 'set_value': 'abc', + }, // string + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":true}', + 'set_value': true, + }, // bool + { + 'key': 'test_write_setting', + 'map': {"test_write_setting": 0}, + 'write_json': '{"test_write_setting":null}', + 'set_value': null, + }, // null + ]; + group('Setting update', () { + test('write setting to file', () async { + for (final data in testsData) { + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeJson = data['write_json'] as String; + String writeContent = ''; + final writeFile = FakeTextFile( + writeContent, + writeFuture: (value) async { + writeContent = value; + }, + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await Setting(key).update(setValue); + expect(writeJson, writeContent); + } + }); + test('update setting value', () async { + for (final data in testsData) { + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.value(), + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await Setting(key).update(setValue); + expect(AppSettings.getSetting(key), setValue); + } + }); + test('call onSuccess if setting saved successfully', () async { + for (final data in testsData) { + bool isSuccess = false; + bool isError = false; + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.value(), + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await Setting(key).update( + setValue, + onSuccess: () { + isSuccess = true; + }, + onError: (_) { + isError = true; + }, + ); + expect(isSuccess, isTrue); + expect(isError, isFalse); + } + }); + test('call onError if setting saved with error', () async { + for (final data in testsData) { + bool isSuccess = false; + bool isError = false; + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.error('write error'), + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await Setting(key).update( + setValue, + onSuccess: () { + isSuccess = true; + }, + onError: (_) { + isError = true; + }, + ); + expect(isSuccess, isFalse); + expect(isError, isTrue); + } + }); + test('call onError if setting is not found', () async { + bool isSuccess = false; + bool isError = false; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.error('write error'), + ); + await AppSettings.initialize( + store: writeFile, + ); + await const Setting('test_not_found_setting').update( + 10, + onSuccess: () { + isSuccess = true; + }, + onError: (_) { + isError = true; + }, + ); + expect(isSuccess, isFalse); + expect(isError, isTrue); + }); + test('call onError if setting is not writable', () async { + bool isSuccess = false; + bool isError = false; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.error('write error'), + ); + await AppSettings.initialize( + readOnly: const FakeJsonMap(Ok({'test_not_write_setting': 0})), + store: writeFile, + ); + await const Setting('test_not_write_setting').update( + 10, + onSuccess: () { + isSuccess = true; + }, + onError: (_) { + isError = true; + }, + ); + expect(isSuccess, isFalse); + expect(isError, isTrue); + }); + test('returns setting to previous value on error', () async { + for (final data in testsData) { + final key = data['key'] as String; + final map = data['map'] as Map; + final setValue = data['set_value'] as dynamic; + final writeFile = FakeTextFile( + '', + writeFuture: (_) => Future.error('write error'), + ); + await AppSettings.initialize( + writable: FakeJsonMap(Ok(map)), + store: writeFile, + ); + await AppSettings.setSetting(key, setValue); + expect(AppSettings.getSetting(key), map[key]); + } + }); + }); +} diff --git a/test/unit/core/entities/app_settings/setting_writable_test.dart b/test/unit/core/entities/app_settings/setting_writable_test.dart new file mode 100644 index 0000000..76ecb7c --- /dev/null +++ b/test/unit/core/entities/app_settings/setting_writable_test.dart @@ -0,0 +1,202 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/hmi_core_app_settings.dart'; +import 'package:hmi_core/src/core/error/failure.dart'; +import 'package:hmi_core/src/core/json/json_map.dart'; +import 'package:hmi_core/src/core/log/log.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +/// +/// Fake implementation of [JsonMap] for tests +class FakeJsonMap implements JsonMap { + final ResultF> _map; + /// + /// Creates [FakeJsonMap] with a given [map]. + const FakeJsonMap(ResultF> map) : _map = map; + // + @override + Future>> get decoded async { + return _map; + } +} +// +void main() { + final List> validReadOnlyMaps = [ + { + 'test_setting_1': 0, + 'test_setting_2': 27, + 'test_setting_3': -123, + }, // int + { + 'test_setting_1': 0.0, + 'test_setting_2': 27.0, + 'test_setting_3': -123.0, + }, // double + ]; + final List> validWritableMaps = [ + { + 'test_writable_setting_1': 0, + 'test_writable_setting_2': 27, + 'test_writable_setting_3': -123, + }, // int + { + 'test_writable_setting_1': 0.0, + 'test_writable_setting_2': 27.0, + 'test_writable_setting_3': -123.0, + }, // double + ]; + group('Setting with writable', () { + Log.initialize(); + const log = Log('Setting '); + test('valid writable data', () async { + for (final writable in validWritableMaps) { + await AppSettings.initialize(writable: FakeJsonMap(Ok(writable))); + for (final setting in writable.entries) { + final int testSetting = Setting(setting.key).toInt; + log.debug('as Int | ${setting.key}: $testSetting'); + } + for (final setting in writable.entries) { + final double testSetting = Setting(setting.key).toDouble; + log.debug('as Double | ${setting.key}: $testSetting'); + } + for (final setting in writable.entries) { + final String testSetting = Setting(setting.key).toString(); + log.debug('as String | ${setting.key}: $testSetting'); + } + expect(const Setting('test_writable_setting_1').toInt, writable['test_writable_setting_1']!.toInt()); + expect(const Setting('test_writable_setting_2').toInt, writable['test_writable_setting_2']!.toInt()); + expect(const Setting('test_writable_setting_3').toInt, writable['test_writable_setting_3']!.toInt()); + expect(const Setting('test_writable_setting_1').toDouble, writable['test_writable_setting_1']!.toDouble()); + expect(const Setting('test_writable_setting_2').toDouble, writable['test_writable_setting_2']!.toDouble()); + expect(const Setting('test_writable_setting_3').toDouble, writable['test_writable_setting_3']!.toDouble()); + expect(const Setting('test_writable_setting_1').toString(), writable['test_writable_setting_1']!.toString()); + expect(const Setting('test_writable_setting_2').toString(), writable['test_writable_setting_2']!.toString()); + expect(const Setting('test_writable_setting_3').toString(), writable['test_writable_setting_3']!.toString()); + } + }); + test('valid read only and writable data', () async { + for (final readOnly in validReadOnlyMaps) { + for (final writable in validWritableMaps) { + await AppSettings.initialize( + readOnly: FakeJsonMap(Ok(readOnly)), + writable: FakeJsonMap(Ok(writable)), + ); + for (final setting in readOnly.entries) { + final int testSetting = Setting(setting.key).toInt; + log.debug('as Int | ${setting.key}: $testSetting'); + } + for (final setting in readOnly.entries) { + final double testSetting = Setting(setting.key).toDouble; + log.debug('as Double | ${setting.key}: $testSetting'); + } + for (final setting in readOnly.entries) { + final String testSetting = Setting(setting.key).toString(); + log.debug('as String | ${setting.key}: $testSetting'); + } + expect(const Setting('test_setting_1').toInt, readOnly['test_setting_1']!.toInt()); + expect(const Setting('test_setting_2').toInt, readOnly['test_setting_2']!.toInt()); + expect(const Setting('test_setting_3').toInt, readOnly['test_setting_3']!.toInt()); + expect(const Setting('test_setting_1').toDouble, readOnly['test_setting_1']!.toDouble()); + expect(const Setting('test_setting_2').toDouble, readOnly['test_setting_2']!.toDouble()); + expect(const Setting('test_setting_3').toDouble, readOnly['test_setting_3']!.toDouble()); + expect(const Setting('test_setting_1').toString(), readOnly['test_setting_1']!.toString()); + expect(const Setting('test_setting_2').toString(), readOnly['test_setting_2']!.toString()); + expect(const Setting('test_setting_3').toString(), readOnly['test_setting_3']!.toString()); + for (final setting in writable.entries) { + final int testSetting = Setting(setting.key).toInt; + log.debug('as Int | ${setting.key}: $testSetting'); + } + for (final setting in writable.entries) { + final double testSetting = Setting(setting.key).toDouble; + log.debug('as Double | ${setting.key}: $testSetting'); + } + for (final setting in writable.entries) { + final String testSetting = Setting(setting.key).toString(); + log.debug('as String | ${setting.key}: $testSetting'); + } + expect(const Setting('test_writable_setting_1').toInt, writable['test_writable_setting_1']!.toInt()); + expect(const Setting('test_writable_setting_2').toInt, writable['test_writable_setting_2']!.toInt()); + expect(const Setting('test_writable_setting_3').toInt, writable['test_writable_setting_3']!.toInt()); + expect(const Setting('test_writable_setting_1').toDouble, writable['test_writable_setting_1']!.toDouble()); + expect(const Setting('test_writable_setting_2').toDouble, writable['test_writable_setting_2']!.toDouble()); + expect(const Setting('test_writable_setting_3').toDouble, writable['test_writable_setting_3']!.toDouble()); + expect(const Setting('test_writable_setting_1').toString(), writable['test_writable_setting_1']!.toString()); + expect(const Setting('test_writable_setting_2').toString(), writable['test_writable_setting_2']!.toString()); + expect(const Setting('test_writable_setting_3').toString(), writable['test_writable_setting_3']!.toString()); + } + } + }); + test('writable key not found', () async { + for (final settings in validWritableMaps) { + await AppSettings.initialize(writable: FakeJsonMap(Ok(settings))); + for (final setting in settings.entries) { + final int testSetting = Setting(setting.key).toInt; + log.debug('as Int | ${setting.key}: $testSetting'); + } + for (final setting in settings.entries) { + final double testSetting = Setting(setting.key).toDouble; + log.debug('as Double | ${setting.key}: $testSetting'); + } + for (final setting in settings.entries) { + final String testSetting = Setting(setting.key).toString(); + log.debug('as String | ${setting.key}: $testSetting'); + } + expect(Setting('test_writable_setting_1111', onError: (err) => 111).toInt, 111); + expect(Setting('test_writable_setting_1222', onError: (err) => -222).toInt, -222); + expect(Setting('test_writable_setting_1.10', onError: (err) => 1.10).toDouble, 1.10); + expect(Setting('test_writable_setting_2.22', onError: (err) => 2.22).toDouble, 2.22); + expect(Setting('test_writable_setting_1234', onError: (err) => '1234').toString(), '1234'); + expect(Setting('test_writable_setting_2345', onError: (err) => '2345').toString(), '2345'); + } + }); + test('valid writable data with factor', () async { + const factors = [0.123, -0.123, 3.54, -5.12]; + for (final factor in factors) { + for (final settings in validWritableMaps) { + await AppSettings.initialize(writable: FakeJsonMap(Ok(settings))); + for (final setting in settings.entries) { + final int testSetting = Setting(setting.key, factor: factor).toInt; + log.debug('as Int | ${setting.key} * $factor: $testSetting'); + } + for (final setting in settings.entries) { + final double testSetting = Setting(setting.key, factor: factor).toDouble; + log.debug('as Double | ${setting.key} * $factor: $testSetting'); + } + const factorConst = 0.123; + const setting = Setting('test_writable_setting_1', factor: factorConst); + expect(setting.toInt, (settings['test_writable_setting_1']! * factor).toInt()); + expect(Setting('test_writable_setting_1', factor: factor).toInt, (settings['test_writable_setting_1']! * factor).toInt()); + expect(Setting('test_writable_setting_2', factor: factor).toInt, (settings['test_writable_setting_2']! * factor).toInt()); + expect(Setting('test_writable_setting_3', factor: factor).toInt, (settings['test_writable_setting_3']! * factor).toInt()); + expect(Setting('test_writable_setting_1', factor: factor).toDouble, (settings['test_writable_setting_1']! * factor).toDouble()); + expect(Setting('test_writable_setting_2', factor: factor).toDouble, (settings['test_writable_setting_2']! * factor).toDouble()); + expect(Setting('test_writable_setting_3', factor: factor).toDouble, (settings['test_writable_setting_3']! * factor).toDouble()); + } + } + }); + test('does not throw with invalid map', () { + final invalidJsonMaps = [ + FakeJsonMap(Err(Failure( + message: 'error', + stackTrace: StackTrace.current, + ))), + FakeJsonMap(Err(Failure( + message: null, + stackTrace: StackTrace.current, + ))), + FakeJsonMap(Err(Failure( + message: '{"validJson":true}', + stackTrace: StackTrace.current, + ))), + ]; + for (final invalidJsonMap in invalidJsonMaps) { + expectLater( + AppSettings.initialize(writable: invalidJsonMap), + completes, + ); + expectLater( + AppSettings.initialize(readOnly: invalidJsonMap, writable: invalidJsonMap), + completes, + ); + } + }); + }); +} diff --git a/test/unit/core/entities/state_colors/state_colors_test.dart b/test/unit/core/entities/state_colors/state_colors_test.dart deleted file mode 100644 index bd32fba..0000000 --- a/test/unit/core/entities/state_colors/state_colors_test.dart +++ /dev/null @@ -1,46 +0,0 @@ -import 'package:flutter/rendering.dart'; -import 'package:flutter_test/flutter_test.dart'; -import 'package:hmi_core/hmi_core.dart'; - -void main() { - test('StateColors test', () { - final colors = { - 'alarm': const Color(0xAA000000), - 'error': const Color(0xAA000001), - 'obsolete': const Color(0xAA000002), - 'invalid': const Color(0xAA000003), - 'timeInvalid': const Color(0xAA000004), - 'lowLevel': const Color(0xAA000005), - 'alarmLowLevel': const Color(0xAA000006), - 'highLevel': const Color(0xAA000007), - 'alarmHighLevel': const Color(0xAA000008), - 'off': const Color(0xAA000009), - 'on': const Color(0xAA000010), - }; - final stateColors = StateColors( - error: colors['error']!, - alarm: colors['alarm']!, - obsolete: colors['obsolete']!, - invalid: colors['invalid']!, - timeInvalid: colors['timeInvalid']!, - lowLevel: colors['lowLevel']!, - alarmLowLevel: colors['alarmLowLevel']!, - highLevel: colors['highLevel']!, - alarmHighLevel: colors['alarmHighLevel']!, - off: colors['off']!, - on: colors['on']!, - ); - expect(stateColors, isA()); - expect(stateColors.error, colors['error']!); - expect(stateColors.alarm, colors['alarm']!); - expect(stateColors.obsolete, colors['obsolete']!); - expect(stateColors.invalid, colors['invalid']!); - expect(stateColors.timeInvalid, colors['timeInvalid']!); - expect(stateColors.lowLevel, colors['lowLevel']!); - expect(stateColors.alarmLowLevel, colors['alarmLowLevel']!); - expect(stateColors.highLevel, colors['highLevel']!); - expect(stateColors.alarmHighLevel, colors['alarmHighLevel']!); - expect(stateColors.off, colors['off']!); - expect(stateColors.on, colors['on']!); - }); -} \ No newline at end of file diff --git a/test/unit/core/error/failure_conctructor_test.dart b/test/unit/core/error/failure_conctructor_test.dart index fecee37..b20ef8e 100644 --- a/test/unit/core/error/failure_conctructor_test.dart +++ b/test/unit/core/error/failure_conctructor_test.dart @@ -4,66 +4,7 @@ import 'package:hmi_core/src/core/error/failure.dart'; void main() { test('Failure creates normally', () { expect( - () => Failure( - message: '', - stackTrace: StackTrace.current, - ), - returnsNormally, - ); - expect( - () => Failure.dataSource( - message: '', - stackTrace: StackTrace.current, - ), - returnsNormally, - ); - expect( - () => Failure.dataObject( - message: '', - stackTrace: StackTrace.current, - ), - returnsNormally, - ); - expect( - () => Failure.dataCollection( - message: '', - stackTrace: StackTrace.current, - ), - returnsNormally, - ); - expect( - () => Failure.auth( - message: '', - stackTrace: StackTrace.current, - ), - returnsNormally, - ); - expect( - () => Failure.convertion( - message: '', - stackTrace: StackTrace.current, - ), - returnsNormally, - ); - expect( - () => Failure.connection( - message: '', - stackTrace: StackTrace.current, - ), - returnsNormally, - ); - expect( - () => Failure.translation( - message: '', - stackTrace: StackTrace.current, - ), - returnsNormally, - ); - expect( - () => Failure.unexpected( - message: '', - stackTrace: StackTrace.current, - ), + () => Failure(''), returnsNormally, ); }); diff --git a/test/unit/core/error/failure_to_string_test.dart b/test/unit/core/error/failure_to_string_test.dart index 88cfa01..bc9d462 100644 --- a/test/unit/core/error/failure_to_string_test.dart +++ b/test/unit/core/error/failure_to_string_test.dart @@ -13,10 +13,7 @@ void main() { ]; for(final message in errorMessages) { expect( - Failure( - message: message, - stackTrace: StackTrace.current, - ).toString(), + Failure(message).toString(), equals(message.toString()), ); } diff --git a/test/unit/core/json/json_list_decoded_test.dart b/test/unit/core/json/json_list_from_string_test.dart similarity index 78% rename from test/unit/core/json/json_list_decoded_test.dart rename to test/unit/core/json/json_list_from_string_test.dart index 7aa557e..0ce74c2 100644 --- a/test/unit/core/json/json_list_decoded_test.dart +++ b/test/unit/core/json/json_list_from_string_test.dart @@ -2,9 +2,10 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hmi_core/src/core/json/json_list.dart'; import 'package:hmi_core/src/core/log/log.dart'; import 'package:hmi_core/src/core/result/result.dart'; +// void main() { Log.initialize(); - group('JsonList decoded', () { + group('JsonList.fromString decoded', () { test('returns valid List on valid jsons', () async { final validIntJsons = [ { @@ -22,12 +23,12 @@ void main() { final jsonList = JsonList.fromString(textJson); final result = await jsonList.decoded; expect(result, isA()); - final decodedJson = (await jsonList.decoded as Ok).value; + final decodedJson = (await jsonList.decoded as Ok).value; expect(decodedJson, equals(parsedList)); } }); test('returns valid List on valid jsons', () async { - final validIntJsons = [ + final validBoolJsons = [ { 'text_json': '[true,false,true]', 'parsed_list': [true, false, true], @@ -37,18 +38,18 @@ void main() { 'parsed_list': [false, false, false], }, ]; - for (final validData in validIntJsons) { + for (final validData in validBoolJsons) { final textJson = validData['text_json'] as String; final parsedList = validData['parsed_list']! as List; final jsonList = JsonList.fromString(textJson); final result = await jsonList.decoded; expect(result, isA()); - final decodedJson = (await jsonList.decoded as Ok).value; + final decodedJson = (await jsonList.decoded as Ok).value; expect(decodedJson, equals(parsedList)); } }); test('returns valid List on valid jsons', () async { - final validIntJsons = [ + final validDoubleJsons = [ { 'text_json': '[0.0,123.45,-1.80e308]', 'parsed_list': [0.0, 123.45, -1.80e308], @@ -58,18 +59,18 @@ void main() { 'parsed_list': [2.23e-308, -2.23e-308, 1.80e308], }, ]; - for (final validData in validIntJsons) { + for (final validData in validDoubleJsons) { final textJson = validData['text_json'] as String; final parsedList = validData['parsed_list']! as List; final jsonList = JsonList.fromString(textJson); final result = await jsonList.decoded; expect(result, isA()); - final decodedJson = (await jsonList.decoded as Ok).value; + final decodedJson = (await jsonList.decoded as Ok).value; expect(decodedJson, equals(parsedList)); } }); test('returns valid List on valid jsons', () async { - final validIntJsons = [ + final validStringJsons = [ { 'text_json': '["0.0","123.45","-1.80e308"]', 'parsed_list': ["0.0", "123.45", "-1.80e308"], @@ -79,37 +80,37 @@ void main() { 'parsed_list': ["abcdefghijklmnopqrstuvwxyz", "1234567890", "!@#\$%^&*()_+-="], }, ]; - for (final validData in validIntJsons) { + for (final validData in validStringJsons) { final textJson = validData['text_json'] as String; final parsedList = validData['parsed_list']! as List; final jsonList = JsonList.fromString(textJson); final result = await jsonList.decoded; expect(result, isA()); - final decodedJson = (await jsonList.decoded as Ok).value; + final decodedJson = (await jsonList.decoded as Ok).value; expect(decodedJson, equals(parsedList)); } }); test('returns Err on invalid jsons', () async { final invalidJsons = [ - { - 'text_json': '{', - }, - { - 'text_json': 'asd', - }, - { - 'text_json': '{"test":123', - }, - { - 'text_json': '[', - }, - ]; + { + 'text_json': '{', + }, + { + 'text_json': 'asd', + }, + { + 'text_json': '{"test":123', + }, + { + 'text_json': '[', + }, + ]; for (final invalidJson in invalidJsons) { final textJson = invalidJson['text_json']!; - final jsonMap = JsonList.fromString(textJson); - final result = await jsonMap.decoded; + final jsonList = JsonList.fromString(textJson); + final result = await jsonList.decoded; expect(result, isA()); } }); }); -} \ No newline at end of file +} diff --git a/test/unit/core/json/json_list_from_text_file_test.dart b/test/unit/core/json/json_list_from_text_file_test.dart new file mode 100644 index 0000000..a9d40da --- /dev/null +++ b/test/unit/core/json/json_list_from_text_file_test.dart @@ -0,0 +1,161 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/src/core/json/json_list.dart'; +import 'package:hmi_core/src/core/log/log.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +import 'json_map_from_text_file_test.dart'; +// +void main() { + Log.initialize(); + group('JsonList.fromTextFile decoded', () { + test('returns valid List on valid jsons', () async { + final validIntJsons = [ + { + 'text_json': '[0,27,-123]', + 'parsed_list': [0, 27, -123], + }, + { + 'text_json': '[4294967295, 2147483647, -2147483648]', + 'parsed_list': [4294967295, 2147483647, -2147483648], + }, + ]; + for (final validData in validIntJsons) { + final textJson = validData['text_json'] as String; + final parsedList = validData['parsed_list']! as List; + final jsonList = JsonList.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonList.decoded; + expect(result, isA()); + final decodedJson = (await jsonList.decoded as Ok).value; + expect(decodedJson, equals(parsedList)); + } + }); + test('returns valid List on valid jsons', () async { + final validBoolJsons = [ + { + 'text_json': '[true,false,true]', + 'parsed_list': [true, false, true], + }, + { + 'text_json': '[false,false,false]', + 'parsed_list': [false, false, false], + }, + ]; + for (final validData in validBoolJsons) { + final textJson = validData['text_json'] as String; + final parsedList = validData['parsed_list']! as List; + final jsonList = JsonList.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonList.decoded; + expect(result, isA()); + final decodedJson = (await jsonList.decoded as Ok).value; + expect(decodedJson, equals(parsedList)); + } + }); + test('returns valid List on valid jsons', () async { + final validDoubleJsons = [ + { + 'text_json': '[0.0,123.45,-1.80e308]', + 'parsed_list': [0.0, 123.45, -1.80e308], + }, + { + 'text_json': '[2.23e-308,-2.23e-308,1.80e308]', + 'parsed_list': [2.23e-308, -2.23e-308, 1.80e308], + }, + ]; + for (final validData in validDoubleJsons) { + final textJson = validData['text_json'] as String; + final parsedList = validData['parsed_list']! as List; + final jsonList = JsonList.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonList.decoded; + expect(result, isA()); + final decodedJson = (await jsonList.decoded as Ok).value; + expect(decodedJson, equals(parsedList)); + } + }); + test('returns valid List on valid jsons', () async { + final validStringJsons = [ + { + 'text_json': '["0.0","123.45","-1.80e308"]', + 'parsed_list': ["0.0", "123.45", "-1.80e308"], + }, + { + 'text_json': '["abcdefghijklmnopqrstuvwxyz","1234567890","!@#\$%^&*()_+-="]', + 'parsed_list': ["abcdefghijklmnopqrstuvwxyz", "1234567890", "!@#\$%^&*()_+-="], + }, + ]; + for (final validData in validStringJsons) { + final textJson = validData['text_json'] as String; + final parsedList = validData['parsed_list']! as List; + final jsonList = JsonList.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonList.decoded; + expect(result, isA()); + final decodedJson = (await jsonList.decoded as Ok).value; + expect(decodedJson, equals(parsedList)); + } + }); + test('returns Err on invalid jsons', () async { + final invalidJsons = [ + { + 'text_json': '{', + }, + { + 'text_json': 'asd', + }, + { + 'text_json': '{"test":123', + }, + { + 'text_json': '[', + }, + ]; + for (final invalidJson in invalidJsons) { + final textJson = invalidJson['text_json']!; + final jsonList = JsonList.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonList.decoded; + expect(result, isA()); + } + }); + test('returns Err on valid non-list json', () async { + final invalidListJsons = [ + { + 'text_json': '{}', + }, + { + 'text_json': 'true', + }, + { + 'text_json': 'false', + }, + { + 'text_json': 'null', + }, + { + 'text_json': '"valid"', + }, + { + 'text_json': '123', + }, + { + 'text_json': '123.45', + }, + ]; + for (final invalidJson in invalidListJsons) { + final textJson = invalidJson['text_json']!; + final jsonList = JsonList.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonList.decoded; + expect(result, isA()); + } + }); + test('does not write to input file', () async { + bool isWritten = false; + final jsonMap = JsonList.fromTextFile( + FakeTextFile( + const Ok('["valid", "json"]'), + writeFuture: (_) async { + isWritten = true; + }, + ), + ); + await jsonMap.decoded; + expect(isWritten, isFalse); + }); + }); +} diff --git a/test/unit/core/json/json_list_from_text_files_test.dart b/test/unit/core/json/json_list_from_text_files_test.dart new file mode 100644 index 0000000..2913d41 --- /dev/null +++ b/test/unit/core/json/json_list_from_text_files_test.dart @@ -0,0 +1,196 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/src/core/error/failure.dart'; +import 'package:hmi_core/src/core/json/json_list.dart'; +import 'package:hmi_core/src/core/log/log.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +import 'json_map_from_text_file_test.dart'; +// +void main() { + Log.initialize(); + group('JsonList.fromTextFiles decoded', () { + test('returns valid List on valid jsons', () async { + final validIntJsons = [ + '[0,27,-123]', + '[4294967295, 2147483647, -2147483648]', + ]; + final parsedList = [0, 27, -123, 4294967295, 2147483647, -2147483648]; + final jsonList = JsonList.fromTextFiles( + validIntJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonList.decoded; + expect(result, isA()); + final decodedJson = (await jsonList.decoded as Ok).value; + expect(decodedJson, equals(parsedList)); + }); + test('returns valid List on valid jsons', () async { + final validBoolJsons = [ + '[true,false,true]', + '[false,false,false]', + ]; + final parsedList = [true, false, true, false, false, false]; + final jsonList = JsonList.fromTextFiles( + validBoolJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonList.decoded; + expect(result, isA()); + final decodedJson = (await jsonList.decoded as Ok).value; + expect(decodedJson, equals(parsedList)); + }); + test('returns valid List on valid jsons', () async { + final validDoubleJsons = [ + '[0.0,123.45,-1.80e308]', + '[2.23e-308,-2.23e-308,1.80e308]', + ]; + final parsedList = [ + 0.0, + 123.45, + -1.80e308, + 2.23e-308, + -2.23e-308, + 1.80e308, + ]; + final jsonList = JsonList.fromTextFiles( + validDoubleJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonList.decoded; + expect(result, isA()); + final decodedJson = (await jsonList.decoded as Ok).value; + expect(decodedJson, equals(parsedList)); + }); + test('returns valid List on valid jsons', () async { + final validStringJsons = [ + '["0.0","123.45","-1.80e308"]', + '["abcdefghijklmnopqrstuvwxyz","1234567890","!@#\$%^&*()_+-="]', + ]; + final parsedList = [ + '0.0', + '123.45', + '-1.80e308', + 'abcdefghijklmnopqrstuvwxyz', + '1234567890', + '!@#\$%^&*()_+-=', + ]; + final jsonList = JsonList.fromTextFiles( + validStringJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonList.decoded; + expect(result, isA()); + final decodedJson = (await jsonList.decoded as Ok).value; + expect(decodedJson, equals(parsedList)); + }); + test('returns Err on invalid jsons', () async { + final invalidJsons = [ + '{', + 'asd', + '{"test":123', + '[', + ]; + final jsonList = JsonList.fromTextFiles( + invalidJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonList.decoded; + expect(result, isA()); + }); + test('returns Err on valid non-list json', () async { + final invalidListJsons = [ + '{}', + 'true', + 'false', + 'null', + '"valid"', + '123', + '123.45', + ]; + final jsonList = JsonList.fromTextFiles( + invalidListJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonList.decoded; + expect(result, isA()); + }); + test('does not write to input file', () async { + bool isWritten = false; + final jsonMap = JsonList.fromTextFile( + FakeTextFile( + const Ok('["valid", "json"]'), + writeFuture: (_) async { + isWritten = true; + }, + ), + ); + await jsonMap.decoded; + expect(isWritten, isFalse); + }); + test('returns Err if any json is invalid', () async { + final invalidJsons = [ + '["valid","json"]', + '{', + 'asd', + '{"test":123', + '[', + ]; + final jsonList = JsonList.fromTextFiles( + invalidJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonList.decoded; + expect(result, isA()); + }); + test('returns Err on TextFile error', () async { + final filesWithError = [ + FakeTextFile(Err(Failure( + message: null, + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '', + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '["valid","json"]', + stackTrace: StackTrace.current, + ))), + ]; + final jsonList = JsonList.fromTextFiles(filesWithError); + final result = await jsonList.decoded; + expect(result, isA()); + }); + test('returns Err if any TextFile with error', () async { + final filesWithError = [ + FakeTextFile(const Ok('["valid","json"]')), + FakeTextFile(Err(Failure( + message: null, + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '', + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '{"validJson":true}', + stackTrace: StackTrace.current, + ))), + ]; + final jsonList = JsonList.fromTextFiles(filesWithError); + final result = await jsonList.decoded; + expect(result, isA()); + }); + test('does not write to input file', () async { + bool isWritten = false; + final jsonMap = JsonList.fromTextFiles([ + FakeTextFile( + const Ok('["valid","json"]'), + writeFuture: (_) async { + isWritten = true; + }, + ), + FakeTextFile( + const Ok('invalid json'), + writeFuture: (_) async { + isWritten = true; + }, + ), + ]); + await jsonMap.decoded; + expect(isWritten, isFalse); + }); + }); +} diff --git a/test/unit/core/json/json_map_decoded_test.dart b/test/unit/core/json/json_map_from_string_test.dart similarity index 72% rename from test/unit/core/json/json_map_decoded_test.dart rename to test/unit/core/json/json_map_from_string_test.dart index 8a0d10b..381e2a0 100644 --- a/test/unit/core/json/json_map_decoded_test.dart +++ b/test/unit/core/json/json_map_from_string_test.dart @@ -2,18 +2,16 @@ import 'package:flutter_test/flutter_test.dart'; import 'package:hmi_core/src/core/json/json_map.dart'; import 'package:hmi_core/src/core/log/log.dart'; import 'package:hmi_core/src/core/result/result.dart'; + +// void main() { Log.initialize(); - group('JsonMap decoded', () { + group('JsonMap.fromString decoded', () { test('returns valid Map on valid jsons', () async { final validIntJsons = [ { 'text_json': '{"value1":0,"value2":27,"value3":-123}', - 'parsed_map': { - 'value1': 0, - 'value2': 27, - 'value3': -123, - }, + 'parsed_map': {'value1': 0, 'value2': 27, 'value3': -123}, }, { 'text_json': '{"value1":4294967295,"value2":2147483647,"value3":-2147483648}', @@ -30,7 +28,7 @@ void main() { final jsonMap = JsonMap.fromString(textJson); final result = await jsonMap.decoded; expect(result, isA()); - final decodedJson = (await jsonMap.decoded as Ok).value; + final decodedJson = (await jsonMap.decoded as Ok).value; expect(decodedJson, equals(parsedMap)); } }); @@ -38,19 +36,11 @@ void main() { final validIntJsons = [ { 'text_json': '{"value1":true,"value2":false,"value3":true}', - 'parsed_map': { - 'value1': true, - 'value2': false, - 'value3': true, - }, + 'parsed_map': {'value1': true, 'value2': false, 'value3': true}, }, { 'text_json': '{"value1":false,"value2":false,"value3":false}', - 'parsed_map': { - 'value1': false, - 'value2': false, - 'value3': false, - }, + 'parsed_map': {'value1': false, 'value2': false, 'value3': false}, }, ]; for (final validData in validIntJsons) { @@ -59,7 +49,7 @@ void main() { final jsonMap = JsonMap.fromString(textJson); final result = await jsonMap.decoded; expect(result, isA()); - final decodedJson = (await jsonMap.decoded as Ok).value; + final decodedJson = (await jsonMap.decoded as Ok).value; expect(decodedJson, equals(parsedMap)); } }); @@ -67,11 +57,7 @@ void main() { final validIntJsons = [ { 'text_json': '{"value1":0.0,"value2":123.45,"value3":-1.80e308}', - 'parsed_map': { - 'value1': 0.0, - 'value2': 123.45, - 'value3': -1.80e308, - }, + 'parsed_map': {'value1': 0.0, 'value2': 123.45, 'value3': -1.80e308}, }, { 'text_json': '{"value1":2.23e-308,"value2":-2.23e-308,"value3":1.80e308}', @@ -88,7 +74,7 @@ void main() { final jsonMap = JsonMap.fromString(textJson); final result = await jsonMap.decoded; expect(result, isA()); - final decodedJson = (await jsonMap.decoded as Ok).value; + final decodedJson = (await jsonMap.decoded as Ok).value; expect(decodedJson, equals(parsedMap)); } }); @@ -117,25 +103,56 @@ void main() { final jsonMap = JsonMap.fromString(textJson); final result = await jsonMap.decoded; expect(result, isA()); - final decodedJson = (await jsonMap.decoded as Ok).value; + final decodedJson = (await jsonMap.decoded as Ok).value; expect(decodedJson, equals(parsedMap)); } }); test('returns Err on invalid jsons', () async { final invalidJsons = [ - { - 'text_json': '{', - }, - { - 'text_json': 'asd', - }, - { - 'text_json': '{"test":123', - }, - { - 'text_json': '[', - }, - ]; + { + 'text_json': '{', + }, + { + 'text_json': 'asd', + }, + { + 'text_json': '{"test":123', + }, + { + 'text_json': '[', + }, + ]; + for (final invalidJson in invalidJsons) { + final textJson = invalidJson['text_json']!; + final jsonMap = JsonMap.fromString(textJson); + final result = await jsonMap.decoded; + expect(result, isA()); + } + }); + test('returns Err on valid non-map jsons', () async { + final invalidJsons = [ + { + 'text_json': '[]', + }, + { + 'text_json': 'true', + }, + { + 'text_json': 'false', + }, + { + 'text_json': 'null', + }, + { + 'text_json': '"valid"', + }, + { + 'text_json': '123', + }, + { + 'text_json': '123.45', + }, + ]; for (final invalidJson in invalidJsons) { final textJson = invalidJson['text_json']!; final jsonMap = JsonMap.fromString(textJson); @@ -144,4 +161,4 @@ void main() { } }); }); -} \ No newline at end of file +} diff --git a/test/unit/core/json/json_map_from_text_file_test.dart b/test/unit/core/json/json_map_from_text_file_test.dart new file mode 100644 index 0000000..19a224f --- /dev/null +++ b/test/unit/core/json/json_map_from_text_file_test.dart @@ -0,0 +1,238 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/hmi_core_failure.dart'; +import 'package:hmi_core/hmi_core_text_file.dart'; +import 'package:hmi_core/src/core/json/json_map.dart'; +import 'package:hmi_core/src/core/log/log.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +/// +/// Fake implementation of [TextFile] +class FakeTextFile implements TextFile { + ResultF _content; + final Future Function(String value)? writeFuture; + /// + /// Creates [FakeTextFile] with a given [content] if provided. + FakeTextFile(ResultF content, {this.writeFuture}) + : _content = content; + // + @override + Future> get content async { + return _content; + } + // + @override + Future write(String text) async { + await writeFuture?.call(text); + _content = Ok(text); + } +} +// +void main() { + Log.initialize(); + group('JsonMap.fromTextFile decoded', () { + test('returns valid Map on valid jsons', () async { + final validIntJsons = [ + { + 'text_json': '{"value1":0,"value2":27,"value3":-123}', + 'parsed_map': { + 'value1': 0, + 'value2': 27, + 'value3': -123, + }, + }, + { + 'text_json': '{"value1":4294967295,"value2":2147483647,"value3":-2147483648}', + 'parsed_map': { + 'value1': 4294967295, + 'value2': 2147483647, + 'value3': -2147483648, + }, + }, + ]; + for (final validData in validIntJsons) { + final textJson = validData['text_json'] as String; + final parsedMap = validData['parsed_map']! as Map; + final jsonMap = JsonMap.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + } + }); + test('returns valid Map on valid jsons', () async { + final validIntJsons = [ + { + 'text_json': '{"value1":true,"value2":false,"value3":true}', + 'parsed_map': { + 'value1': true, + 'value2': false, + 'value3': true, + }, + }, + { + 'text_json': '{"value1":false,"value2":false,"value3":false}', + 'parsed_map': { + 'value1': false, + 'value2': false, + 'value3': false, + }, + }, + ]; + for (final validData in validIntJsons) { + final textJson = validData['text_json'] as String; + final parsedMap = validData['parsed_map']! as Map; + final jsonMap = JsonMap.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + } + }); + test('returns valid Map on valid jsons', () async { + final validIntJsons = [ + { + 'text_json': '{"value1":0.0,"value2":123.45,"value3":-1.80e308}', + 'parsed_map': { + 'value1': 0.0, + 'value2': 123.45, + 'value3': -1.80e308, + }, + }, + { + 'text_json': '{"value1":2.23e-308,"value2":-2.23e-308,"value3":1.80e308}', + 'parsed_map': { + 'value1': 2.23e-308, + 'value2': -2.23e-308, + 'value3': 1.80e308, + }, + }, + ]; + for (final validData in validIntJsons) { + final textJson = validData['text_json'] as String; + final parsedMap = validData['parsed_map']! as Map; + final jsonMap = + JsonMap.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + } + }); + test('returns valid Map on valid jsons', () async { + final validIntJsons = [ + { + 'text_json': '{"value1":"0.0","value2":"123.45","value3":"-1.80e308"}', + 'parsed_map': { + 'value1': '0.0', + 'value2': '123.45', + 'value3': '-1.80e308', + }, + }, + { + 'text_json': '{"value1":"abcdefghijklmnopqrstuvwxyz","value2":"1234567890","value3":"!@#\$%^&*()_+-="}', + 'parsed_map': { + 'value1': 'abcdefghijklmnopqrstuvwxyz', + 'value2': '1234567890', + 'value3': '!@#\$%^&*()_+-=', + }, + }, + ]; + for (final validData in validIntJsons) { + final textJson = validData['text_json'] as String; + final parsedMap = validData['parsed_map']! as Map; + final jsonMap = JsonMap.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + } + }); + test('returns Err on invalid jsons', () async { + final invalidJsons = [ + { + 'text_json': '{', + }, + { + 'text_json': 'asd', + }, + { + 'text_json': '{"test":123', + }, + { + 'text_json': '[', + }, + ]; + for (final invalidJson in invalidJsons) { + final textJson = invalidJson['text_json']!; + final jsonMap = JsonMap.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonMap.decoded; + expect(result, isA()); + } + }); + // + test('returns Err on valid non-map jsons', () async { + final invalidMapJsons = [ + { + 'text_json': '[]', + }, + { + 'text_json': 'true', + }, + { + 'text_json': 'false', + }, + { + 'text_json': 'null', + }, + { + 'text_json': '"valid"', + }, + { + 'text_json': '123', + }, + { + 'text_json': '123.45', + }, + ]; + for (final invalidJson in invalidMapJsons) { + final textJson = invalidJson['text_json']!; + final jsonMap = JsonMap.fromTextFile(FakeTextFile(Ok(textJson))); + final result = await jsonMap.decoded; + expect(result, isA()); + } + }); + test('returns Err on TextFile error', () async { + final filesWithError = [ + FakeTextFile(Err(Failure( + message: null, + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '', + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '{"validJson":true}', + stackTrace: StackTrace.current, + ))), + ]; + for (final file in filesWithError) { + final jsonMap = JsonMap.fromTextFile(file); + final result = await jsonMap.decoded; + expect(result, isA()); + } + }); + }); + test('does not write to input file', () async { + bool isWritten = false; + final jsonMap = JsonMap.fromTextFile( + FakeTextFile( + const Ok('{"validJson":true}'), + writeFuture: (_) async { + isWritten = true; + }, + ), + ); + await jsonMap.decoded; + expect(isWritten, isFalse); + }); +} diff --git a/test/unit/core/json/json_map_from_text_files_test.dart b/test/unit/core/json/json_map_from_text_files_test.dart new file mode 100644 index 0000000..5a3efdf --- /dev/null +++ b/test/unit/core/json/json_map_from_text_files_test.dart @@ -0,0 +1,237 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:hmi_core/hmi_core_failure.dart'; +import 'package:hmi_core/hmi_core_text_file.dart'; +import 'package:hmi_core/src/core/json/json_map.dart'; +import 'package:hmi_core/src/core/log/log.dart'; +import 'package:hmi_core/src/core/result/result.dart'; +/// +/// Fake implementation of [TextFile] +class FakeTextFile implements TextFile { + ResultF _content; + final Future Function(String value)? writeFuture; + /// + /// Creates [FakeTextFile] with a given [content] if provided. + FakeTextFile(ResultF content, {this.writeFuture}) + : _content = content; + // + @override + Future> get content async { + return _content; + } + // + @override + Future write(String text) async { + await writeFuture?.call(text); + _content = Ok(text); + } +} +// +void main() { + Log.initialize(); + group('JsonMap.fromTextFiles decoded', () { + test('returns valid Map on valid jsons', () async { + final validIntJsons = [ + '{"value1":0,"value2":27,"value3":-123}', + '{"value4":4294967295,"value5":2147483647,"value6":-2147483648}', + ]; + final parsedMap = { + 'value1': 0, + 'value2': 27, + 'value3': -123, + 'value4': 4294967295, + 'value5': 2147483647, + 'value6': -2147483648, + }; + final jsonMap = JsonMap.fromTextFiles( + validIntJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + }); + test('returns valid Map on valid jsons', () async { + final validBoolJsons = [ + '{"value1":true,"value2":false,"value3":true}', + '{"value4":false,"value5":false,"value6":false}', + ]; + final parsedMap = { + 'value1': true, + 'value2': false, + 'value3': true, + 'value4': false, + 'value5': false, + 'value6': false, + }; + final jsonMap = JsonMap.fromTextFiles( + validBoolJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + }); + test('returns valid Map on valid jsons', () async { + final validDoubleJsons = [ + '{"value1":0.0,"value2":123.45,"value3":-1.80e308}', + '{"value4":2.23e-308,"value5":-2.23e-308,"value6":1.80e308}', + ]; + final parsedMap = { + 'value1': 0.0, + 'value2': 123.45, + 'value3': -1.80e308, + 'value4': 2.23e-308, + 'value5': -2.23e-308, + 'value6': 1.80e308, + }; + final jsonMap = JsonMap.fromTextFiles( + validDoubleJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + }); + test('returns valid Map on valid jsons', () async { + final validStringJsons = [ + '{"value1":"0.0","value2":"123.45","value3":"-1.80e308"}', + '{"value4":"abcdefghijklmnopqrstuvwxyz","value5":"1234567890","value6":"!@#\$%^&*()_+-="}', + ]; + final parsedMap = { + 'value1': '0.0', + 'value2': '123.45', + 'value3': '-1.80e308', + 'value4': 'abcdefghijklmnopqrstuvwxyz', + 'value5': '1234567890', + 'value6': '!@#\$%^&*()_+-=', + }; + final jsonMap = JsonMap.fromTextFiles( + validStringJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + }); + test('rewrites values in order of passed files', () async { + final validJsons = [ + '{"value1":1,"value2":1,"value3":1}', + '{"value2":2,"value3":2}', + '{"value3":3}', + ]; + final parsedMap = { + 'value1': 1, + 'value2': 2, + 'value3': 3, + }; + final jsonMap = JsonMap.fromTextFiles( + validJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonMap.decoded; + expect(result, isA()); + final decodedJson = (await jsonMap.decoded as Ok).value; + expect(decodedJson, equals(parsedMap)); + }); + test('returns Err on invalid jsons', () async { + final invalidJsons = [ + '{', + 'asd', + '{"test":123', + '[', + ]; + final jsonMap = JsonMap.fromTextFiles( + invalidJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonMap.decoded; + expect(result, isA()); + }); + test('returns Err on valid non-map jsons', () async { + final invalidMapJsons = [ + '[]', + 'true', + 'false', + 'null', + '"valid"', + '123', + '123.45', + ]; + final jsonMap = JsonMap.fromTextFiles( + invalidMapJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonMap.decoded; + expect(result, isA()); + }); + test('returns Err if any json is invalid', () async { + final invalidJsons = [ + '{"validJson":true}', + '{', + 'asd', + '{"test":123', + '[', + ]; + final jsonMap = JsonMap.fromTextFiles( + invalidJsons.map((json) => FakeTextFile(Ok(json))).toList(), + ); + final result = await jsonMap.decoded; + expect(result, isA()); + }); + test('returns Err on TextFile error', () async { + final filesWithError = [ + FakeTextFile(Err(Failure( + message: null, + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '', + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '{"validJson":true}', + stackTrace: StackTrace.current, + ))), + ]; + final jsonMap = JsonMap.fromTextFiles(filesWithError); + final result = await jsonMap.decoded; + expect(result, isA()); + }); + test('returns Err if any TextFile with error', () async { + final filesWithError = [ + FakeTextFile(const Ok('{"validJson":true}')), + FakeTextFile(Err(Failure( + message: null, + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '', + stackTrace: StackTrace.current, + ))), + FakeTextFile(Err(Failure( + message: '{"validJson":true}', + stackTrace: StackTrace.current, + ))), + ]; + final jsonMap = JsonMap.fromTextFiles(filesWithError); + final result = await jsonMap.decoded; + expect(result, isA()); + }); + test('does not write to input file', () async { + bool isWritten = false; + final jsonMap = JsonMap.fromTextFiles([ + FakeTextFile( + const Ok('{"validJson":true}'), + writeFuture: (_) async { + isWritten = true; + }, + ), + FakeTextFile( + const Ok('invalid json'), + writeFuture: (_) async { + isWritten = true; + }, + ), + ]); + await jsonMap.decoded; + expect(isWritten, isFalse); + }); + }); +} diff --git a/test/unit/core/log/log_level_test.dart b/test/unit/core/log/log_level_test.dart index f3856ed..4c67a65 100644 --- a/test/unit/core/log/log_level_test.dart +++ b/test/unit/core/log/log_level_test.dart @@ -5,6 +5,7 @@ void main() { group('LogLevel test', () { const target = { 'all': ['ALL', 0], + 'trace': ['TRACE', 400], 'debug': ['DEBUG', 500], 'config': ['CONFIG', 700], 'info': ['INFO', 800], diff --git a/test/unit/core/result/result_constructor_test.dart b/test/unit/core/result/result_constructor_test.dart index a6e1987..965a34a 100644 --- a/test/unit/core/result/result_constructor_test.dart +++ b/test/unit/core/result/result_constructor_test.dart @@ -15,7 +15,7 @@ void main() { for(final value in testData) { expect( () => Err( - Failure(message: value, stackTrace: StackTrace.current), + Failure(value), ), returnsNormally, ); diff --git a/test/unit/core/result_old/result_constructor_test.dart b/test/unit/core/result_old/result_constructor_test.dart index adff5e2..91f7935 100644 --- a/test/unit/core/result_old/result_constructor_test.dart +++ b/test/unit/core/result_old/result_constructor_test.dart @@ -14,7 +14,7 @@ void main() { test('completes if error is provided', () { expect( () => Result( - error: Failure(message: '', stackTrace: StackTrace.current), + error: Failure(''), ), returnsNormally, ); diff --git a/test/unit/core/result_old/result_data_test.dart b/test/unit/core/result_old/result_data_test.dart index baf0f06..a4e5c76 100644 --- a/test/unit/core/result_old/result_data_test.dart +++ b/test/unit/core/result_old/result_data_test.dart @@ -8,7 +8,7 @@ void main() { group('Result data', () { const resultWithData = Result(data: 'test'); final resultWithError = Result( - error: Failure(message: '', stackTrace: StackTrace.current), + error: Failure(''), ); test('throws if no data', () { expect(() => resultWithError.data, throwsA(isA())); diff --git a/test/unit/core/result_old/result_error_test.dart b/test/unit/core/result_old/result_error_test.dart index 546511e..7c3fa6c 100644 --- a/test/unit/core/result_old/result_error_test.dart +++ b/test/unit/core/result_old/result_error_test.dart @@ -8,7 +8,7 @@ void main() { group('Result error', () { const resultWithData = Result(data: 'test'); final resultWithError = Result( - error: Failure(message: '', stackTrace: StackTrace.current), + error: Failure(''), ); test('throws if object created without error', () { expect(() => resultWithData.error, throwsA(isA())); diff --git a/test/unit/core/result_old/result_fold_test.dart b/test/unit/core/result_old/result_fold_test.dart index cdaef07..89c27ed 100644 --- a/test/unit/core/result_old/result_fold_test.dart +++ b/test/unit/core/result_old/result_fold_test.dart @@ -8,11 +8,11 @@ void main() { group('Result fold', () { const resultWithData = Result(data: 'test'); final resultWithError = Result( - error: Failure(message: '', stackTrace: StackTrace.current), + error: Failure(''), ); final resultWithDataAndError = Result( data: 'test', - error: Failure(message: '', stackTrace: StackTrace.current), + error: Failure(''), ); const valueIfData = 1; const valueIfError = 0; diff --git a/test/unit/core/result_old/result_has_data_test.dart b/test/unit/core/result_old/result_has_data_test.dart index cf466d3..7cdf009 100644 --- a/test/unit/core/result_old/result_has_data_test.dart +++ b/test/unit/core/result_old/result_has_data_test.dart @@ -8,7 +8,7 @@ void main() { group('Result hasData', () { const resultWithData = Result(data: 'test'); final resultWithError = Result( - error: Failure(message: '', stackTrace: StackTrace.current), + error: Failure(''), ); test('returns true if data is provided', () { expect(resultWithData.hasData, true); diff --git a/test/unit/core/result_old/result_has_error_test.dart b/test/unit/core/result_old/result_has_error_test.dart index e5b12dd..a14d0fb 100644 --- a/test/unit/core/result_old/result_has_error_test.dart +++ b/test/unit/core/result_old/result_has_error_test.dart @@ -8,7 +8,7 @@ void main() { group('Result hasError', () { const resultWithData = Result(data: 'test'); final resultWithError = Result( - error: Failure(message: '', stackTrace: StackTrace.current), + error: Failure(''), ); test('returns true if error is provided', () { expect(resultWithError.hasError, true);