From: Hamatoma Date: Sat, 3 Oct 2020 10:03:40 +0000 (+0200) Subject: unittests complete (except some error situation) X-Git-Url: https://gitweb.hamatoma.de/?a=commitdiff_plain;h=b0bdc7e842cb53a31c1b64e8e6ea526924b478d8;p=flutter_bones.git unittests complete (except some error situation) --- diff --git a/Coverage.sh b/Coverage.sh index fa9044d..0f927f2 100755 --- a/Coverage.sh +++ b/Coverage.sh @@ -1,4 +1,8 @@ #! /bin/sh +if [ $(id -u) = 0 ]; then + echo "+++ not working as root" +else flutter test --coverage rm -Rf coverage/html/* genhtml --show-details --output-directory=coverage/html coverage/lcov.info +fi diff --git a/lib/src/helper/settings.dart b/lib/src/helper/settings.dart index 82523e0..67bb01b 100644 --- a/lib/src/helper/settings.dart +++ b/lib/src/helper/settings.dart @@ -25,10 +25,7 @@ class Settings { return _instance; } - Settings.internal(this.widgetConfiguration, this.logger) { - BaseConfiguration widgetConfiguration = - BaseConfiguration(mapWidgetData, logger); - } + Settings.internal(this.widgetConfiguration, this.logger); /// Sets the locale code. /// [locale] the info about localisation diff --git a/lib/src/model/button_model.dart b/lib/src/model/button_model.dart index a8eea1a..2b0d8f5 100644 --- a/lib/src/model/button_model.dart +++ b/lib/src/model/button_model.dart @@ -2,13 +2,6 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bones/flutter_bones.dart'; -enum ButtonModelType { - cancel, - custom, - search, - store, -} - typedef ButtonOnPressed = void Function(String name); class ButtonModel extends WidgetModel { @@ -19,20 +12,29 @@ class ButtonModel extends WidgetModel { List options; ButtonModelType buttonModelType; - ButtonModel(SectionModel section, PageModel page, this.map, BaseLogger logger) - : super(section, page, WidgetModelType.button, logger); - VoidCallback onPressed; + VoidCallback onLongPressed; ValueChanged onHighlightChanged; + ButtonModel(SectionModel section, PageModel page, this.map, BaseLogger logger) + : super(section, page, WidgetModelType.button, logger); + + /// Dumps a the instance into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer.write( + ' button $name: text: options: $text ${listToString(options)}\n'); + return stringBuffer; + } + /// Returns the name including the names of the parent @override String fullName() => '${section.name}.$name'; /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous(map, 'buttonType name options text widgetType'.split(' ')); + checkSuperfluousAttributes( + map, 'buttonType label name options text widgetType'.split(' ')); name = parseString('name', map, required: true); text = parseString('text', map); buttonModelType = @@ -46,3 +48,10 @@ class ButtonModel extends WidgetModel { @override String widgetName() => name; } + +enum ButtonModelType { + cancel, + custom, + search, + store, +} diff --git a/lib/src/model/checkbox_model.dart b/lib/src/model/checkbox_model.dart index 1d88809..40f1721 100644 --- a/lib/src/model/checkbox_model.dart +++ b/lib/src/model/checkbox_model.dart @@ -6,15 +6,23 @@ class CheckboxModel extends FieldModel { final Map map; - CheckboxModel( - SectionModel section, PageModel page, this.map, BaseLogger logger) + CheckboxModel(SectionModel section, PageModel page, this.map, + BaseLogger logger) : super(section, page, map, WidgetModelType.checkbox, logger); /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous(map, 'name label options toolTip widgetType'.split(' ')); + checkSuperfluousAttributes( + map, 'name label options toolTip widgetType'.split(' ')); super.parse(); options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); } + + /// Dumps the instance into a [StringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer.write( + ' checkbox $name: text: options: ${listToString(options)}\n'); + return stringBuffer; + } } diff --git a/lib/src/model/combobox_model.dart b/lib/src/model/combobox_model.dart index b393d5f..5f6e98d 100644 --- a/lib/src/model/combobox_model.dart +++ b/lib/src/model/combobox_model.dart @@ -15,17 +15,22 @@ class ComboboxModel extends FieldModel { SectionModel section, PageModel page, this.map, BaseLogger logger) : super(section, page, map, WidgetModelType.combobox, logger); + /// Dumps the instance into a [StringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer.write( + ' combobox $name: texts: ${listToString(texts)} options: ${options.join(' ')}\n'); + return stringBuffer; + } + /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous( + checkSuperfluousAttributes( map, 'name label dataType options texts toolTip widgetType values' .split(' ')); super.parse(); texts = parseStringList('texts', map); - values = dataType == DataType.string - ? parseStringList('values', map) - : parseValueList('values', map, dataType); + values = parseValueList('values', map, dataType); options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); } diff --git a/lib/src/model/empty_line_model.dart b/lib/src/model/empty_line_model.dart index abee90f..92236be 100644 --- a/lib/src/model/empty_line_model.dart +++ b/lib/src/model/empty_line_model.dart @@ -11,13 +11,19 @@ class EmptyLineModel extends WidgetModel { SectionModel section, PageModel page, this.map, BaseLogger logger) : super(section, page, WidgetModelType.emptyLine, logger); + /// Dumps the instance into a [StringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer.write(' emptyLine: options: ${options.join(' ')}\n'); + return stringBuffer; + } + /// Returns the name including the names of the parent @override String fullName() => '${section.name}.${widgetName()}'; /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous(map, 'options widgetType'.split(' ')); + checkSuperfluousAttributes(map, 'options widgetType'.split(' ')); options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); } diff --git a/lib/src/model/field_model.dart b/lib/src/model/field_model.dart index 0fed9f8..1d81dd3 100644 --- a/lib/src/model/field_model.dart +++ b/lib/src/model/field_model.dart @@ -2,7 +2,7 @@ import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bones/flutter_bones.dart'; -class FieldModel extends WidgetModel { +abstract class FieldModel extends WidgetModel { static final regExprOptions = RegExp(r'^(undef|readonly|disabled|password|required)$'); String name; @@ -37,14 +37,21 @@ class FieldModel extends WidgetModel { /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous( - map, - 'fieldType dataType label maxSize name options rows toolTip ' - .split(' ')); name = parseString('name', map, required: true); label = parseString('label', map, required: false); toolTip = parseString('toolTip', map, required: false); + dataType = parseEnum('dataType', map, DataType.values); + if (dataType == null) { + switch (widgetModelType) { + case WidgetModelType.checkbox: + dataType = DataType.bool; + break; + default: + dataType = DataType.string; + break; + } + } } @override diff --git a/lib/src/model/model_base.dart b/lib/src/model/model_base.dart index b379ef2..ce8007e 100644 --- a/lib/src/model/model_base.dart +++ b/lib/src/model/model_base.dart @@ -15,7 +15,7 @@ abstract class ModelBase { bool rc = false; options.forEach((element) { if (regExpr.firstMatch(element) == null) { - logger.error('unbekannte Feldoption $element in ${fullName()}'); + logger.error('wrong option $element in ${fullName()}'); rc = true; } }); @@ -25,13 +25,10 @@ abstract class ModelBase { /// Tests a [map] for superfluous [keys]. /// Keys of the [map] that are not listed in [keys] will be logged as errors. /// Keys that do not have the type String are logged as errors. - void checkSuperfluous(Map map, List keys) { + void checkSuperfluousAttributes(Map map, List keys) { map.keys.forEach((element) { - if (element.runtimeType != String) { - logger.error( - 'wrong attribute data type ${element.runtimeType} in ${fullName()}'); - } else if (!keys.contains(element)) { - logger.error('unknown attribute "${element}" in ${fullName()}'); + if (!keys.contains(element)) { + logger.error('unknown attribute "$element" in ${fullName()}'); } }); } @@ -39,6 +36,12 @@ abstract class ModelBase { /// Returns the name including the names of the parent String fullName(); + /// Converts a [list] of strings to a string. + String listToString(List list) { + final rc = list == null ? 'null' : list.join(' '); + return rc; + } + /// Fetches an entry from a map addressed by a [key]. /// An error is logged if [required] is true and the map does not contain the key. T parseEnum(String key, Map map, List values, @@ -46,38 +49,48 @@ abstract class ModelBase { T rc; if (!map.containsKey(key)) { if (required) { - logger.error('missing $key in ${fullName()}'); + logger.error('missing attribute "$key" in ${fullName()}'); } } else { final text = map[key] as String; rc = StringUtils.stringToEnum(text, values); + if (rc == null) { + logger.error( + 'unknown value $text of attribute "$key" in ${fullName()}. Allowed:' + + values.fold( + '', + (previousValue, element) => previousValue += + ' ' + StringUtils.enumToString(element))); + } } return rc; } /// Fetches an entry from a map addressed by a [key]. /// An error is logged if [required] is true and the map does not contain the key. - int parseInt(String key, Map map, {required: bool}) { - int rc; - if (!map.containsKey(key)) { - if (required) { - logger.error('missing $key in ${fullName()}'); - } else { - if (map[key].runtimeType == int) { - rc = map[key]; - } else if (map[key].runtimeType == String) { - if (Validation.isInt(map[key])) { - rc = StringUtils.asInt(map[key]); - } else { - logger - .error('not an integer: ${map[key]} map[$key] in {fullName()}'); - } - } else { - logger.error('not an integer: ${map[key]} map[$key] in {fullName()}'); - } - } - } - return rc; + int parseInt(String key, Map map, {bool required = false}) { + int rc; + if (!map.containsKey(key)) { + if (required) { + logger.error('missing $key in ${fullName()}'); + } else { + final value = map[key]; + if (value != null) { + if (value.runtimeType == int) { + rc = map[key]; + } else if (value.runtimeType == String) { + if (Validation.isInt(value)) { + rc = StringUtils.asInt(value); + } else { + logger.error('not an integer: $value map[$key] in {fullName()}'); + } + } else { + logger.error('not an integer: $value map[$key] in {fullName()}'); + } + } + } + } + return rc; } /// Fetches an entry from a map addressed by a [key]. @@ -85,7 +98,13 @@ abstract class ModelBase { List parseOptions(String key, Map map) { List rc = []; if (map.containsKey(key)) { - rc = map[key].split(';'); + final value = map[key]; + if (value.runtimeType == String && value.isNotEmpty) { + rc = value.split(';'); + } else { + logger.error( + 'wrong datatype of options ${value.runtimeType} in ${fullName()}'); + } } return rc; } @@ -138,19 +157,24 @@ abstract class ModelBase { if (dataType == null) { dataType = DataType.string; } - final strings = parseStringList(key, map, required: required); - final rc = strings.map((item) { - var rc2; - switch (dataType) { - case DataType.int: - rc2 = StringUtils.asInt(item); - break; - default: - logger.error('unknown dataType in parseValueList()'); - rc2 = item; - } - return rc2; - }); + List rc; + if (ModelBase.isList(map[key])) { + rc = map[key]; + } else { + final strings = parseStringList(key, map, required: required); + rc = strings.map((item) { + var rc2; + switch (dataType) { + case DataType.int: + rc2 = StringUtils.asInt(item); + break; + default: + logger.error('unknown dataType in parseValueList()'); + rc2 = item; + } + return rc2; + }); + } return rc; } diff --git a/lib/src/model/module_model.dart b/lib/src/model/module_model.dart index 8b65cb1..d53761c 100644 --- a/lib/src/model/module_model.dart +++ b/lib/src/model/module_model.dart @@ -1,4 +1,5 @@ import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter/cupertino.dart'; import 'package:flutter_bones/flutter_bones.dart'; import 'package:meta/meta.dart'; @@ -20,6 +21,15 @@ class ModuleModel extends ModelBase { pageMap[page.name] = page; } + /// Dumps the internal structure into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer.write('= module $name: options: ${options.join(' ')}\n'); + for (var page in pages) { + page.dump(stringBuffer); + } + return stringBuffer; + } + /// Returns the name including the names of the parent @override String fullName() => name; @@ -30,7 +40,7 @@ class ModuleModel extends ModelBase { /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous(map, 'module options pages'.split(' ')); + checkSuperfluousAttributes(map, 'module options pages'.split(' ')); name = parseString('module', map, required: true); if (!map.containsKey('pages')) { logger.error('Module $name: missing pages'); diff --git a/lib/src/model/page_model.dart b/lib/src/model/page_model.dart index 35d1996..c49ab8d 100644 --- a/lib/src/model/page_model.dart +++ b/lib/src/model/page_model.dart @@ -1,6 +1,9 @@ import 'package:dart_bones/dart_bones.dart'; +import 'package:flutter/foundation.dart'; import 'package:flutter_bones/flutter_bones.dart'; +typedef FilterWidget = bool Function(WidgetModel item); + /// Represents one screen of the application. class PageModel extends ModelBase { static final regExprOptions = RegExp(r'^(unknown)$'); @@ -10,19 +13,100 @@ class PageModel extends ModelBase { PageModelType pageModelType; final List sections = []; List options; + @protected + final fields = {}; + @protected + final buttons = {}; + final widgets = []; PageModel(this.module, this.map, BaseLogger logger) : super(logger); + /// Adds a [button] to the [this.buttons]. + /// If already defined and error is logged. + void addButton(ButtonModel button) { + final name = button.name; + if (buttons.containsKey(name)) { + logger.error('button ${button.fullName()} already defined: ' + + buttons[name].fullName()); + } else { + buttons[name] = button; + } + } + + /// Adds a [field] to the [this.fields]. + /// If already defined and error is logged. + void addField(FieldModel field) { + final name = field.name; + if (fields.containsKey(name)) { + logger.error('field ${field.fullName()} already defined: ' + + fields[name].fullName()); + } else { + fields[name] = field; + } + } + + /// Dumps the internal structure into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer + .write('== page $name: $pageModelType options: ${options.join(' ')}\n'); + for (var section in sections) { + section.dump(stringBuffer); + } + return stringBuffer; + } + /// Returns the name including the names of the parent @override String fullName() => '${module.name}.$name'; + /// Returns a field by [name] or null on error. + ButtonModel getButton(String name, {bool required = true}) { + ButtonModel rc; + if (!buttons.containsKey(name)) { + if (required) { + logger.error('missing button $name in page ${fullName()}'); + } + } else { + rc = buttons[name]; + } + return rc; + } + + /// Returns a field by [name] or null on error. + FieldModel getField(String name, {bool required = true}) { + FieldModel rc; + if (!fields.containsKey(name)) { + if (required) { + logger.error('missing field $name in page ${fullName()}'); + } + } else { + rc = fields[name]; + } + return rc; + } + + /// Returns a list of widget models (in order as defined) matching the [filter]. + /// If [filter] is none all widgets will be returned. + List getWidgets(FilterWidget filter) { + final rc = filter == null + ? widgets + : widgets.fold([], (prevValue, item) { + if (filter(item)) { + prevValue.add(item); + } + return prevValue; + }); + return rc; + } + /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous(map, 'name options pageType sections'.split(' ')); + checkSuperfluousAttributes( + map, 'name options pageType sections'.split(' ')); name = parseString('name', map, required: true); - pageModelType = - parseEnum('pageType', map, PageModelType.values); + pageModelType = parseEnum( + 'pageType', map, PageModelType.values, + required: true); options = parseOptions('options', map); if (!map.containsKey('sections')) { logger.error('missing sections in ${fullName()}'); @@ -38,13 +122,15 @@ class PageModel extends ModelBase { checkOptionsByRegExpr(options, regExprOptions); } - /// Returns a list of Pages constructed by the Json like [map]. - static void parseList( - ModuleModel module, List> map, BaseLogger logger) { - for (var item in map) { + /// Returns a list of Pages constructed by the Json like [list]. + static void parseList(ModuleModel module, List list, + BaseLogger logger) { + // Note: "List list" does not work with json maps. + for (var item in list) { if (!ModelBase.isMap(item)) { module.logger.error( - 'curious item in section list of ${module.fullName()}: ${StringUtils.limitString("$item", 80)}'); + 'curious item in section list of ${module.fullName()}: ${StringUtils + .limitString("$item", 80)}'); } else { final page = PageModel(module, item, logger); page.parse(); diff --git a/lib/src/model/section_model.dart b/lib/src/model/section_model.dart index bc8aaa4..63546eb 100644 --- a/lib/src/model/section_model.dart +++ b/lib/src/model/section_model.dart @@ -16,6 +16,18 @@ class SectionModel extends WidgetModel { BaseLogger logger) : super(section, page, WidgetModelType.section, logger); + /// Dumps the internal structure into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer.write( + ' = section $name: $sectionModelType options: ${options.join( + ' ')} [\n'); + for (var child in children) { + child.dump(stringBuffer); + } + stringBuffer.write(' ] # ${fullName()}\n'); + return stringBuffer; + } + /// Returns the name including the names of the parent @override String fullName() => @@ -23,7 +35,7 @@ class SectionModel extends WidgetModel { /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous( + checkSuperfluousAttributes( map, 'children fields name options sectionType widgetType'.split(' ')); sectionModelType = parseEnum( 'sectionType', map, SectionModelType.values); @@ -42,15 +54,15 @@ class SectionModel extends WidgetModel { int no = 0; for (var child in childrenList) { no++; - if (!ModelBase.isMap(childrenList)) { + if (!ModelBase.isMap(child)) { logger .error('child $no of "children" is not a map in ${fullName()}: ' - '${StringUtils.limitString(child.toString(), 80)}'); + '${StringUtils.limitString(child.toString(), 80)}'); } else { if (!child.containsKey('widgetType')) { logger.error( 'child $no of "children" does not have "widgetType" in ${fullName()}: ' - '${StringUtils.limitString(child.toString(), 80)}'); + '${StringUtils.limitString(child.toString(), 80)}'); } else { final widgetType = StringUtils.stringToEnum( child['widgetType'].toString(), WidgetModelType.values); @@ -61,6 +73,7 @@ class SectionModel extends WidgetModel { break; case WidgetModelType.combobox: widget = ComboboxModel(this, page, child, logger); + page.addField(widget); break; case WidgetModelType.textField: widget = TextFieldModel(this, page, child, logger); @@ -82,6 +95,20 @@ class SectionModel extends WidgetModel { } if (widget != null) { children.add(widget); + widget.parse(); + page.widgets.add(widget); + switch (widget.widgetModelType) { + case WidgetModelType.button: + page.addButton(widget); + break; + case WidgetModelType.textField: + case WidgetModelType.combobox: + case WidgetModelType.checkbox: + page.addField(widget); + break; + default: + break; + } } } } @@ -91,16 +118,16 @@ class SectionModel extends WidgetModel { } } - /// Returns a list of SectionModel constructed by the Json like [map]. + /// Returns a list of SectionModel constructed by the Json like [list]. static void parseSections(PageModel page, SectionModel section, - List> map, BaseLogger logger) { - final rc = []; + List list, BaseLogger logger) { int no = 0; - for (var item in map) { + for (var item in list) { no++; if (!ModelBase.isMap(item)) { page.logger.error( - 'curious item in section list of ${page.fullName()}: ${StringUtils.limitString("$item", 80)}'); + 'curious item in section list of ${page.fullName()}: ${StringUtils + .limitString("$item", 80)}'); } else { final current = SectionModel(no, page, null, item, logger); current.parse(); diff --git a/lib/src/model/text_field_model.dart b/lib/src/model/text_field_model.dart index 7b39972..2fc2691 100644 --- a/lib/src/model/text_field_model.dart +++ b/lib/src/model/text_field_model.dart @@ -7,6 +7,7 @@ class TextFieldModel extends FieldModel { RegExp(r'^(readonly|disabled|password|required|unique)$'); int maxSize; int rows; + var value; FormFieldValidator validator; @@ -18,13 +19,31 @@ class TextFieldModel extends FieldModel { SectionModel section, PageModel page, this.map, BaseLogger logger) : super(section, page, map, WidgetModelType.textField, logger); + /// Dumps the internal structure into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer + .write(' textField $name: options: ${listToString(options)}\n'); + return stringBuffer; + } + /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous( - map, 'dataType label maxSize name options rows toolTip'.split(' ')); + checkSuperfluousAttributes( + map, + 'dataType label maxSize name options rows toolTip value widgetType' + .split(' ')); super.parse(); maxSize = parseInt('maxSize', map, required: false); rows = parseInt('rows', map, required: false); + switch (dataType) { + case DataType.int: + case DataType.reference: + value = parseInt('value', map); + break; + default: + value = parseString('value', map); + break; + } options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); } diff --git a/lib/src/model/text_model.dart b/lib/src/model/text_model.dart index fbb4333..d4a668d 100644 --- a/lib/src/model/text_model.dart +++ b/lib/src/model/text_model.dart @@ -15,9 +15,16 @@ class TextModel extends WidgetModel { @override String fullName() => '${section.name}.text$id'; + /// Dumps the internal structure into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer) { + stringBuffer + .write(' text $id text: $text: options: ${options.join(' ')}\n'); + return stringBuffer; + } + /// Parses the map and stores the data in the instance. void parse() { - checkSuperfluous(map, 'options text widgetType'.split(' ')); + checkSuperfluousAttributes(map, 'options text widgetType'.split(' ')); text = parseString('text', map, required: true); options = parseOptions('options', map); checkOptionsByRegExpr(options, regExprOptions); diff --git a/lib/src/model/widget_model.dart b/lib/src/model/widget_model.dart index c802b0c..bf77cb5 100644 --- a/lib/src/model/widget_model.dart +++ b/lib/src/model/widget_model.dart @@ -14,6 +14,11 @@ abstract class WidgetModel extends ModelBase { this.id = ++lastId; } + /// Dumps the internal structure into a [stringBuffer] + StringBuffer dump(StringBuffer stringBuffer); + + void parse(); + String widgetName(); } diff --git a/lib/src/widget/view.dart b/lib/src/widget/view.dart index e022755..b93990a 100644 --- a/lib/src/widget/view.dart +++ b/lib/src/widget/view.dart @@ -127,6 +127,9 @@ class View { case WidgetModelType.textField: rc.add(textField(child)); break; + case WidgetModelType.button: + rc.add(button(child)); + break; case WidgetModelType.emptyLine: rc.add(emptyLine(child)); break; @@ -147,5 +150,6 @@ class View { break; } } + return rc; } } diff --git a/test/helpers/settings_test.dart b/test/helpers/settings_test.dart index 27c7f6a..5413fd6 100644 --- a/test/helpers/settings_test.dart +++ b/test/helpers/settings_test.dart @@ -1,4 +1,3 @@ -import 'package:dart_bones/dart_bones.dart'; import 'package:flutter/material.dart'; import 'package:flutter_bones/flutter_bones.dart'; import 'package:test/test.dart'; @@ -8,12 +7,10 @@ void main() { 'help': {'de': 'Hilfe'}, 'wrong name %{0}': {'de': 'falscher name %{0}'} }; - final logger = MemoryLogger(LEVEL_FINE); setUpAll(() => Settings.setLocaleByNames(language: 'en')); group('Settings', () { test('basic', () { Settings.setLocale(Locale('de', 'de')); - final settings = Settings(logger); expect(Settings.translate('abc', map), equals('abc')); expect(Settings.translate('help', map), equals('help')); expect( diff --git a/test/model/model_test.dart b/test/model/model_test.dart index f99000c..60e97b8 100644 --- a/test/model/model_test.dart +++ b/test/model/model_test.dart @@ -1,3 +1,5 @@ +import 'dart:convert'; + import 'package:dart_bones/dart_bones.dart'; import 'package:flutter_bones/flutter_bones.dart'; import 'package:test/test.dart'; @@ -6,47 +8,225 @@ void main() { final logger = MemoryLogger(LEVEL_FINE); group('module', () { test('module', () { - final module = Demo1(logger); + logger.clear(); + final module = Demo1(cloneOfMap(userModel), logger); module.parse(); final errors = logger.errors; expect(errors.length, equals(0)); final page = module.pageByName('create'); expect(page?.fullName(), equals('demo1.create')); expect(module.fullName(), equals('demo1')); + final dump = module.dump(StringBuffer()).toString(); + expect(dump, '''= module demo1: options: +== page create: PageModelType.create options: + = section simpleForm1: SectionModelType.simpleForm options: [ + textField user: options: required unique + button buttonStore: text: options: null + ] # create.simpleForm1 +'''); + final userField = page.getField('user'); + expect(userField, isNotNull); + expect(userField.widgetName(), 'user'); + userField.value = 'Hi'; + expect(userField.value, equals('Hi')); + expect(userField.fullName(), equals('create.user')); + expect(userField.page.fullName(), equals(page.fullName())); + expect(userField.section, isNotNull); + final button = page.getButton('buttonStore'); + expect(button, isNotNull); + expect(button.section, equals(userField.section)); + expect(button.fullName(), 'simpleForm1.buttonStore'); + expect(button.widgetName(), 'buttonStore'); + }); + }); + group('ModelBase', () { + test('errors', () { + logger.clear(); + final map = cloneOfMap(userModel); + final field = { + 'widgetType': 'textField', + 'name': 'year', + 'label': 'Year', + 'options': 'required;blubb', + 'dataType': 'int' + }; + map['pages'][0]['sections'][0]['children'][0] = field; + var module = Demo1(map, logger); + module.parse(); + var errors = logger.errors; + expect(errors.length, equals(1)); + expect(logger.contains('blub'), isTrue); + logger.clearErrors(); + field['options'] = 3; + module = Demo1(map, logger); + module.parse(); + errors = logger.errors; + expect(errors.length, equals(1)); + expect(logger.contains('wrong datatype'), isTrue); + // === + logger.clear(); + field['newfeature'] = 3; + field['dataType'] = 'number'; + field['options'] = ''; + module = Demo1(map, logger); + module.parse(); + errors = logger.errors; + expect(errors.length, equals(3)); + expect(logger.contains('unknown attribute "newfeature"'), isTrue); + expect( + logger.contains( + 'unknown value number of attribute "dataType" in create.year'), + isTrue); + expect(logger.contains('wrong datatype of options String in create.year'), + isTrue); + // === + logger.clear(); + field.removeWhere( + (key, value) => key == 'newfeature' || key == 'dataType'); + map['pages'][0].remove('pageType'); + module = Demo1(map, logger); + module.parse(); + map['pages'][0]['pageType'] = 'simpleForm'; + errors = logger.errors; + expect(errors.length, equals(2)); + expect(logger.contains('missing attribute \"pageType\" in demo1.create'), + isTrue); + }); + }); + group('CheckboxModel', () { + logger.clear(); + test('errors', () { + final map = cloneOfMap(userModel); + final field = { + 'widgetType': 'checkbox', + 'name': 'hidden', + 'label': 'Hidden', + 'options': 'required', + }; + map['pages'][0]['sections'][0]['children'][0] = field; + var module = Demo1(map, logger); + module.parse(); + var errors = logger.errors; + expect(errors.length, equals(2)); + final checkbox = module.pageByName('create').getField('hidden'); + expect(checkbox, isNotNull); + expect(checkbox.dataType, DataType.bool); + final dump = module.dump(StringBuffer()).toString(); + expect(dump, equals('''= module demo1: options: +== page create: PageModelType.create options: + = section simpleForm1: SectionModelType.simpleForm options: [ + checkbox hidden: text: options: required + button buttonStore: text: options: null + ] # create.simpleForm1 +''')); + }); + }); + group('ComboboxModel', () { + logger.clear(); + test('basic', () { + final map = cloneOfMap(userModel); + final field = { + 'widgetType': 'combobox', + 'name': 'class', + 'label': 'Class', + 'options': 'undef', + 'texts': ';bad;OK;good', + 'values': [-1, 0, 1], + 'dataType': 'int', + }; + map['pages'][0]['sections'][0]['children'][0] = field; + var module = Demo1(map, logger); + module.parse(); + var errors = logger.errors; + expect(errors.length, equals(2)); + final combobox = module.pageByName('create').getField('class'); + expect(combobox, isNotNull); + expect(combobox.dataType, DataType.int); + final dump = module.dump(StringBuffer()).toString(); + expect(dump, equals('''= module demo1: options: +== page create: PageModelType.create options: + = section simpleForm1: SectionModelType.simpleForm options: [ + combobox class: texts: bad OK good options: undef + button buttonStore: text: options: null + ] # create.simpleForm1 +''')); + }); + }); + group('Non field widgets', () { + logger.clear(); + test('basic', () { + final map = cloneOfMap(userModel); + final list = [ + { + 'widgetType': 'emptyLine', + }, + {'widgetType': 'text', 'text': '*Hi world*', 'options': 'rich'}, + ]; + map['pages'][0]['sections'][0]['children'] = list; + var module = Demo1(map, logger); + module.parse(); + var errors = logger.errors; + expect(errors.length, equals(3)); + final dump = module.dump(StringBuffer()).toString(); + expect(dump, equals('''= module demo1: options: +== page create: PageModelType.create options: + = section simpleForm1: SectionModelType.simpleForm options: [ + emptyLine: options: + text 24 text: *Hi world*: options: rich + ] # create.simpleForm1 +''')); + final page = module.pageByName('create'); + final allWidgets = page.getWidgets(null); + expect(allWidgets.length, equals(2)); + final names = allWidgets.fold('', (prevValue, element) { + return prevValue += + ' ' + element.widgetName() + '/' + element.fullName(); + }); + expect(names.contains('null'), isFalse); + final nonFieldWidgets = page.getWidgets((item) => [ + WidgetModelType.text, + WidgetModelType.emptyLine + ].contains(item.widgetModelType)); + expect(nonFieldWidgets.length, equals(2)); }); }); } -class Demo1 extends ModuleModel { - static final model = { - "module": "demo1", - "pages": [ - { - "name": "create", - "pageType": "create", - "sections": [ - { - "sectionType": "simpleForm", - "children": [ - { - "widgetType": "textField", - "name": "user", - "label": "User", - "options": "required;unique", - }, - { - "widgetType": "button", - "name": "buttonStore", - "label": "Save", - }, - ] - } - ] - }, - ], - }; +final userModel = { + 'module': 'demo1', + 'pages': [ + { + 'name': 'create', + 'pageType': 'create', + 'sections': [ + { + 'sectionType': 'simpleForm', + 'children': [ + { + 'widgetType': 'textField', + 'name': 'user', + 'label': 'User', + 'options': 'required;unique', + }, + { + 'widgetType': 'button', + 'name': 'buttonStore', + 'label': 'Save', + }, + ] + } + ] + }, + ], +}; - Demo1(BaseLogger logger) : super(model, logger); +Map cloneOfMap(Map map) { + final rc = json.decode(json.encode(map)); + return rc; +} + +class Demo1 extends ModuleModel { + Demo1(Map map, BaseLogger logger) : super(map, logger); /// Returns the name including the names of the parent @override