]> gitweb.hamatoma.de Git - flutter_bones.git/commitdiff
unittests complete (except some error situation)
authorHamatoma <author@hamatoma.de>
Sat, 3 Oct 2020 10:03:40 +0000 (12:03 +0200)
committerHamatoma <author@hamatoma.de>
Sat, 3 Oct 2020 10:10:44 +0000 (12:10 +0200)
17 files changed:
Coverage.sh
lib/src/helper/settings.dart
lib/src/model/button_model.dart
lib/src/model/checkbox_model.dart
lib/src/model/combobox_model.dart
lib/src/model/empty_line_model.dart
lib/src/model/field_model.dart
lib/src/model/model_base.dart
lib/src/model/module_model.dart
lib/src/model/page_model.dart
lib/src/model/section_model.dart
lib/src/model/text_field_model.dart
lib/src/model/text_model.dart
lib/src/model/widget_model.dart
lib/src/widget/view.dart
test/helpers/settings_test.dart
test/model/model_test.dart

index fa9044d44f4e46efd2fd9e0d70674fd8dd6bd445..0f927f2496c6735231b50f6a64f6d657a7ceb42b 100755 (executable)
@@ -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
index 82523e06c42f9e3ca4e782e6a82666f74fd2c507..67bb01b17c97e8001db510b35b32813077f6c0c2 100644 (file)
@@ -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
index a8eea1adce1357d90942d294b590e7d42430c64c..2b0d8f5dc1616db99364d825eda383f6f32a0cf2 100644 (file)
@@ -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<String> options;
   ButtonModelType buttonModelType;
 
-  ButtonModel(SectionModel section, PageModel page, this.map, BaseLogger logger)
-      : super(section, page, WidgetModelType.button, logger);
-
   VoidCallback onPressed;
+
   VoidCallback onLongPressed;
   ValueChanged<bool> 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,
+}
index 1d88809c7079348113ce553cce77ec7603dd2e3e..40f17218a05e04a09161f506674c5619c760751d 100644 (file)
@@ -6,15 +6,23 @@ class CheckboxModel extends FieldModel {
 
   final Map<String, dynamic> 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;
+  }
 }
index b393d5f045f7f51e54e6ae8782b9fdc2f96e6a51..5f6e98d476b702963e79a31ad7ac0450c19fcec2 100644 (file)
@@ -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);
   }
index abee90f46980d42a6c3a22114a6ac2478986a4ff..92236be1e53ca4cf911ad72fc770e7c64a672dab 100644 (file)
@@ -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);
   }
index 0fed9f8cbc10a6520e68db746886a66e98a18092..1d81dd3fcbe64a16abe2ed72571478e0724af043 100644 (file)
@@ -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>('dataType', map, DataType.values);
+    if (dataType == null) {
+      switch (widgetModelType) {
+        case WidgetModelType.checkbox:
+          dataType = DataType.bool;
+          break;
+        default:
+          dataType = DataType.string;
+          break;
+      }
+    }
   }
 
   @override
index b379ef267c221cf71bca6e9ce5a89cffe4ddafdc..ce8007ef82c382df4fde58be76f9dd0df5d0be81 100644 (file)
@@ -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<String, dynamic> map, List<String> keys) {
+  void checkSuperfluousAttributes(Map<String, dynamic> map, List<String> 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<String> 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<T>(String key, Map<String, dynamic> 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<T>(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<String, dynamic> 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<String, dynamic> 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<String> parseOptions(String key, Map<String, dynamic> map) {
     List<String> 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<dynamic> 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;
   }
 
index 8b65cb1be00a0c08ccf0ce4c265b0153e87f6d83..d53761c1c46e67d9af522f5caec9a71312402922 100644 (file)
@@ -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');
index 35d199623e9646c6f613c8cd6d1d83575a3efe72..c49ab8d5afed4a67e57009a7eb5e16173be8f414 100644 (file)
@@ -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<SectionModel> sections = [];
   List<String> options;
+  @protected
+  final fields = <String, FieldModel>{};
+  @protected
+  final buttons = <String, ButtonModel>{};
+  final widgets = <WidgetModel>[];
 
   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<dynamic> 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<PageModelType>('pageType', map, PageModelType.values);
+    pageModelType = parseEnum<PageModelType>(
+        '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<String, dynamic>> 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<dynamic> list,
+      BaseLogger logger) {
+    // Note: "List<Map<String, dynamic> 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();
index bc8aaa48c2738b5f709e8b9cd53918bceb6c6a2e..63546eb757870e7d7f452c90de343814c8d8215f 100644 (file)
@@ -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<SectionModelType>(
         '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<WidgetModelType>(
                   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<String, dynamic>> map, BaseLogger logger) {
-    final rc = <SectionModel>[];
+      List<dynamic> 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();
index 7b3997261bc60251c4eea64fd24528b5f5e7d8c3..2fc2691c8b0242c770e8e995929694b3e789b91b 100644 (file)
@@ -7,6 +7,7 @@ class TextFieldModel extends FieldModel {
       RegExp(r'^(readonly|disabled|password|required|unique)$');
   int maxSize;
   int rows;
+  var value;
 
   FormFieldValidator<String> 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);
   }
index fbb4333eebe0f6645546376967c97bfdcef7898a..d4a668d05ae7e5c3d02dc29912927b516db69089 100644 (file)
@@ -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);
index c802b0ccab39350dd2b833474533fcbf33c581f5..bf77cb5e3f692d84e8d8d6f6536da8db8d85ff2b 100644 (file)
@@ -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();
 }
 
index e0227556911c30c0f8a55ac26403b75d979b61ed..b93990ae1f4b26a2c652dfd34d8d74d4de07f7c1 100644 (file)
@@ -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;
   }
 }
index 27c7f6ae5d8021065e9444fa137cd8a824e9f8ce..5413fd67a31128d549d4faeb288325c2f3531c7b 100644 (file)
@@ -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(
index f99000c31a1a7e654f6bd48ddc2433d183e25fb3..60e97b8e95652c78a596172fdf3e707e8f9d9d86 100644 (file)
@@ -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 = <String, dynamic>{
+        '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 = <String, dynamic>{
+        '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 = <String, dynamic>{
+        '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 = <String, dynamic>{
-    "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 = <String, dynamic>{
+  '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<String, dynamic> cloneOfMap(Map<String, dynamic> map) {
+  final rc = json.decode(json.encode(map));
+  return rc;
+}
+
+class Demo1 extends ModuleModel {
+  Demo1(Map<String, dynamic> map, BaseLogger logger) : super(map, logger);
 
   /// Returns the name including the names of the parent
   @override