feat: Infrastructure for automatic control widget creation for configuration fields

Change-Id: I87a0eb6ba5b2128c779f04c8971106b1c8fbd33f
diff --git a/test/test_config_ui.py b/test/test_config_ui.py
new file mode 100644
index 0000000..688f3ca
--- /dev/null
+++ b/test/test_config_ui.py
@@ -0,0 +1,75 @@
+#  SPDX-License-Identifier: MPL-2.0
+#    Copyright (c) 2022 Philipp Le <philipp@philipple.de>.
+#  This Source Code Form is subject to the terms of the Mozilla Public
+#  License, v. 2.0. If a copy of the MPL was not distributed with this
+#  file, You can obtain one at https://mozilla.org/MPL/2.0/.
+
+import unittest
+from typing import List
+from pydantic import conint, confloat
+from tkinter import ttk
+from enum import IntEnum
+
+from dcs.config.store import Store, ConfigObject, ConfigControlFrame
+from dcs.config.ui import ui_create
+
+
+class ExampleSubFrame(ConfigControlFrame):
+    pass
+
+
+@ui_create
+class ExampleSub(ConfigObject):
+    id: conint(ge=0, lt=10000) = 1
+    title: str = 'test'
+
+    def make_config_widget(self, parent: ttk.Widget) -> ConfigControlFrame:
+        frm = ExampleSubFrame(parent)
+        return frm
+
+
+
+class ExampleIntEnum(IntEnum):
+    Option1 = 1
+    Option2 = 2
+
+
+@ui_create
+class ExampleConfig(ConfigObject):
+    _KEY = 'example'
+    id: conint(ge=0, lt=10) = 1
+    name: str = 'Hello'
+    sub: List[ExampleSub] = [
+        ExampleSub(id=1, title='item 1'),
+        ExampleSub(id=2, title='item 2'),
+    ]
+    int_option: ExampleIntEnum = ExampleIntEnum.Option2
+    flt: confloat(ge=0.0, lt=10.0, multiple_of=0.01) = 0.12
+
+
+class UITest(unittest.TestCase):
+    def test_deserialize(self):
+        in_data = {
+            'example': {
+                'id': 0,
+                'name': 'Test1',
+                'sub': [
+                    {
+                        'id': 0,
+                        'title': 'Title1',
+                    },
+                    {
+                        'id': 1,
+                        'title': 'Title2',
+                    },
+                ]
+            }
+        }
+
+        loaded_store = Store.load_obj(in_data, 'default')
+
+        store = Store.get_instance('default')
+        self.assertEqual(id(loaded_store), id(store))
+
+        cfg: ExampleConfig = store.get_config(ExampleConfig)
+        self.assertIn('ui_create_id', dir(cfg))