From ac5f5dbb92cca2b3dcde6b9e89864fefd51981cc Mon Sep 17 00:00:00 2001
From: Timm Fitschen <t.fitschen@indiscale.com>
Date: Thu, 20 Feb 2020 18:06:00 +0100
Subject: [PATCH] TST: some tests for new_module

---
 src/core/js/new_module.js             | 159 ++++++++++++++++----------
 src/core/js/webcaosdb.js              |   7 ++
 test/core/index.html                  |   1 +
 test/core/js/modules/new_module.js.js |  55 +++++++--
 4 files changed, 154 insertions(+), 68 deletions(-)

diff --git a/src/core/js/new_module.js b/src/core/js/new_module.js
index 12a95603..0784b2e8 100644
--- a/src/core/js/new_module.js
+++ b/src/core/js/new_module.js
@@ -33,30 +33,68 @@
  * @requires jQuery
  * @requires logger
  */
-var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly) {
+var new_module = function ($, logger, is_in_view_port, load_config, getEntityPath, connection) {
+
+
+    /**
+     * Check if an entity has a path attribute and one of a set of extensions.
+     *
+     * Note: the array of extensions must contain only lower-case strings.
+     *
+     * @param {HTMLElement} entity
+     * @param {string[]} extensions - an array of file extesions, e.g. `jpg`.
+     * @return {boolean} true iff the entity has a path with one of the
+     *     extensionss.
+     */
+    const _path_has_file_extension = function (entity, extensions) {
+        var path = getEntityPath(entity);
+        if (path) {
+            for (ext in extensions) {
+                if(path.toLowerCase().endsWith(ext)) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
+    const _create_video_preview = function (entity) {
+        var path = connection.getFileSystemPath() + getEntityPath(entity);
+        return $(`<video control="controls"><source src="${path}"/></video>`)[0];
+    }
+
+    const _create_picture_preview = function (entity) {
+        var path = connection.getFileSystemPath() + getEntityPath(entity);
+        return $(`<img class="entity-image-preview" style="max-width: 200px; max-height=140px;" src="${path}"/>`)[0];
+    }
+
+    var fallback_preview = undefined;
 
     /**
      * TODO Please adhere to JSDoc standards: https://jsdoc.app/
      */
     const _default_creators = [
-        { // TODO pictures (remove from entity.xsl)
+        { // pictures
             id: "_default_creators.pictures",
-            is_applicable: (entity) => false,
-            create: undefined,
+            is_applicable: (entity) => _path_has_file_extension(
+                entity, ["jpg", "png", "gif", "svg"]),
+            create: _create_picture_preview
         },
-        { // TODO videos (remove from entity.xsl)
+        { // videos
             id: "_default_creators.videos",
-            is_applicable: (entity) => false,
-            create: undefined,
+            is_applicable: (entity) => _path_has_file_extension(
+                entity, ["mp4", "mov", "webm"]),
+            create: _create_video_preview,
         },
-        { // default
+        { // fallback
             id: "_default_creators.fallback",
             is_applicable: (entity) => true,
-            create: (entity) => "Fall-back preview.",
+            create: (entity) => fallback_preview,
         },
 
     ];
 
+    const previewShownEvent = new Event("new_module.preview.shown");
     const previewReadyEvent = new Event("new_module.preview.ready");
 
     /**
@@ -64,6 +102,7 @@ var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly)
      */
     const _css_class_preview_container = "caosdb-f-new_module-container";
     const _css_class_preview_container_resolvable = "caosdb-f-new_module-container-resolvable";
+    const _css_class_preview_container_button = "caosdb-f-new_module-container-button";
 
     /**
      * TODO Please adhere to JSDoc standards: https://jsdoc.app/
@@ -99,10 +138,16 @@ var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly)
      *     must have a (deep) child with class `caosdb-f-new_module-container`.
      */
     var set_preview_container = function (entity, element) {
-        var preview_container = get_preview_container(entity);
-        if (preview_container) {
-            $(preview_container).empty();
-            $(preview_container).append(element);
+        var preview_container = $(get_preview_container(entity));
+        if (preview_container[0]) {
+            preview_container.empty();
+            var buttons = preview_container.siblings(`.${_css_class_preview_container_button}`);
+            if (element) {
+                buttons.css({"visibility": "initial"});
+                preview_container.append(element);
+            } else {
+                buttons.css({"visibility": "hidden"});
+            }
         } else {
             logger.error(new Error("Could not find the preview container."));
         }
@@ -129,7 +174,9 @@ var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly)
             set_preview_container(entity, wait);
             const result = await preview;
             set_preview_container(entity, result);
-            entity.dispatchEvent(previewReadyEvent);
+            if (result) {
+                entity.dispatchEvent(previewReadyEvent);
+            }
         } catch (err) {
             logger.error(err);
             const err_msg = "An error occured while loading this preview";
@@ -187,12 +234,14 @@ var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly)
      */
     var add_preview_container = function (entity) {
         const button_show = $('<button class="btn btn-xs"><span class="glyphicon glyphicon-menu-down"/> Show Preview</button>')
-            .css({width: "100%"});
-        const button_hide = $('<button class="btn btn-xs"><span class="glyphicon glyphicon-menu-up"/> Show Preview</button>')
             .css({width: "100%"})
+            .addClass(_css_class_preview_container_button);
+        const button_hide = $('<button class="btn btn-xs"><span class="glyphicon glyphicon-menu-up"/> Hide Preview</button>')
+            .css({width: "100%"})
+            .addClass(_css_class_preview_container_button)
             .hide();
         const style = { padding: "0px 10px" };
-        const container = $(`<div class="collapse ${_css_class_preview_container}"/>`)
+        const container = $(`<div class="collapse"/>`)
             .addClass(_css_class_preview_container)
             .addClass(_css_class_preview_container_resolvable)
             .css(style);
@@ -208,6 +257,7 @@ var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly)
         }
         button_show.click(show);
         button_hide.click(hide);
+        container.on("shown.bs.collapse", () => { container[0].dispatchEvent(previewShownEvent); });
         $(entity).append(container);
         $(entity).append(button_show);
         $(entity).append(button_hide);
@@ -226,26 +276,24 @@ var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly)
      *     created.
      */
     var root_preview_handler = async function (entity) {
-
-
-        const preview = root_preview_creator(entity);
-        if (preview) {
+        var container = $(get_preview_container(entity) || add_preview_container(entity));
+        if (container.hasClass(_css_class_preview_container_resolvable)) {
+            container.removeClass(_css_class_preview_container_resolvable);
+            const preview = root_preview_creator(entity);
             set_preview(entity, preview);
         }
     }
 
     /**
-     * Trigger the root_preview_handler for all entities when the view port has
-     * changed with a delay.
+     * Trigger the root_preview_handler for all entities within the view port
+     * when the view port.
      */
     var root_preview_handler_trigger = function () {
         var entities = $(".caosdb-entity-panel,.caosdb-entity-preview");
         for (let entity of entities) {
-            var container = $(get_preview_container(entity) || add_preview_container(entity));
-            // TODO viewport + 1000 px
 
-            if (container.hasClass(_css_class_preview_container_resolvable) && is_in_view_port(container[0])) {
-                container.removeClass(_css_class_preview_container_resolvable);
+            // TODO viewport + 1000 px for earlier loading
+            if (is_in_view_port(entity)) {
                 root_preview_handler(entity);
             }
         }
@@ -291,48 +339,35 @@ var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly)
             throw new Error("Wrong version in config.");
         }
 
-        _creators = config.creators;
-    };
-
-    /**
-     * TODO Please adhere to JSDoc standards: https://jsdoc.app/
-     */
-    var load_config = async function () {
-        var user_config = await retrieve_config("json/new_module.json");
-
-        var config = {
-            version: user_config.version,
-            delay: (user_config.delay || 500),
-            creators: [],
-        };
-
         // append/load creators
-        for (let c of user_config.creators) {
+        _creators.splice(0, _creators.length);
+        for (let c of config.creators) {
             // TODO check deps
-            config.creators.push({
+            _creators.push({
                 id: c.id,
-                is_applicable: eval(c.is_applicable),
-                create: eval(c.create)
+                is_applicable: typeof c.is_applicable === "function" ? c.is_applicable : eval(c.is_applicable),
+                create: typeof c.create === "function" ? c.create : eval(c.create)
             });
         }
 
         // append default creators
-        config.creators = config.creators.concat(_default_creators);
-
-        return config;
+        for (let c of _default_creators) {
+            _creators.push(c);
+        }
     };
 
+
     /**
      * TODO Please adhere to JSDoc standards: https://jsdoc.app/
      */
-    var init = async function () {
+    var init = async function (config) {
         logger.info("new_module initialized");
 
         try {
-            const config = await load_config();
-            await configure(config);
+            let _config = config || await load_config("json/new_module.json");
+            await configure(_config);
 
-            init_watcher(config.delay || 500, root_preview_handler_trigger);
+            init_watcher(_config.delay || 500, root_preview_handler_trigger);
 
             // trigger the whole thing for the first time
             root_preview_handler_trigger();
@@ -344,22 +379,28 @@ var new_module = function ($, logger, is_in_view_port, retrieve_config, plotly)
     }
 
     return {
+        previewShownEvent: previewShownEvent,
         previewReadyEvent: previewReadyEvent,
         init: init,
         init_watcher: init_watcher,
         configure: configure,
+        add_preview_container: add_preview_container,
+        _creators: _creators,
+        _css_class_preview_container,
+        _css_class_preview_container_button,
+        _css_class_preview_container_resolvable,
     }
-}($, log.getLogger("new_module"), resolve_references.is_in_viewport_vertically, load_config, Plotly);
+}($, log.getLogger("new_module"), resolve_references.is_in_viewport_vertically, load_config, getEntityPath, connection);
 
 
 /**
  * Helper for plotly
  */
-var plotly_preview = function (logger, new_module, preview, plotly) {
+var plotly_preview = function (logger, new_module, plotly) {
 
     var create_plot = function (data) {
         var div = $('<div/>')[0];
-        plotly.newPlot(div, data, { margin: { t: 0}, height: 200}, {responsive: true});
+        plotly.newPlot(div, data, { margin: { t: 0}, height: 400, widht: 400 }, {responsive: true});
         return div;
     }
 
@@ -371,10 +412,10 @@ var plotly_preview = function (logger, new_module, preview, plotly) {
     }
 
     var init = function () {
-        window.addEventListener(preview.showPreviewEvent.type,
-            resize_plots_event_handler, true);
         window.addEventListener(new_module.previewReadyEvent.type,
             resize_plots_event_handler, true);
+        window.addEventListener(new_module.previewShownEvent.type,
+            resize_plots_event_handler, true);
     }
 
     return {
@@ -382,7 +423,7 @@ var plotly_preview = function (logger, new_module, preview, plotly) {
         init: init,
     };
 
-}(log.getLogger("plotly_preview"), new_module, preview, Plotly);
+}(log.getLogger("plotly_preview"), new_module, Plotly);
 
 
 // this will be replaced by require.js in the future.
diff --git a/src/core/js/webcaosdb.js b/src/core/js/webcaosdb.js
index 56f00429..87b9e744 100644
--- a/src/core/js/webcaosdb.js
+++ b/src/core/js/webcaosdb.js
@@ -289,6 +289,13 @@ this.connection = new function() {
             return base;
         }
 
+        /**
+         * Return the root path of the file system resource.
+         */
+        this.getFileSystemPath = function () {
+            return connection.getBasePath() + "FileSystem/";
+        }
+
         /**
          * Return a full URI for these entities.
          *
diff --git a/test/core/index.html b/test/core/index.html
index 0e49a671..5311de07 100644
--- a/test/core/index.html
+++ b/test/core/index.html
@@ -38,6 +38,7 @@
   <script src="js/bootstrap-select.js"></script>
   <script src="js/bootstrap-autocomplete.min.js"></script>
   <script src="js/webcaosdb.js"></script>
+  <script src="js/plotly.js"></script>
   <script>
       caosdb_modules.auto_init = false;
       log.setLevel("trace");
diff --git a/test/core/js/modules/new_module.js.js b/test/core/js/modules/new_module.js.js
index 34e5cdf5..abef0572 100644
--- a/test/core/js/modules/new_module.js.js
+++ b/test/core/js/modules/new_module.js.js
@@ -24,10 +24,32 @@
 
 var new_module_test_suite = function ($, new_module, QUnit) {
 
+    var test_config = { "version": 0.1,
+      "deps": ["getParents", "getEntityName"],
+      "creators": [
+        { "id": "test.success",
+          "is_applicable": "(entity) => getParents(entity).map(par => par.name).includes('TestPreviewRecordType') && getEntityName(entity) === 'TestPreviewRecord-success'",
+          "create": "(entity) => 'SUCCESS'"
+        },
+        { "id": "test.error",
+          "is_applicable": "(entity) => getParents(entity).map(par => par.name).includes('TestPreviewRecordType') && getEntityName(entity) === 'TestPreviewRecord-error'",
+          "create": "(entity) => new Promise((res,rej) => {rej('Test Error');})"
+        },
+        { "id": "test.load-forever",
+          "is_applicable": "(entity) => getParents(entity).map(par => par.name).includes('TestPreviewRecordType') && getEntityName(entity) === 'TestPreviewRecord-load-forever'",
+          "create": "(entity) => new Promise((res,rej) => {})"
+        },
+        { "id": "test.success-2",
+          "is_applicable": "(entity) => getParents(entity).map(par => par.name).includes('TestPreviewRecordType') && getEntityName(entity) !== 'TestPreviewRecord-fall-back'",
+          "create": "(entity) => { return plotly_preview.create_plot([{x: [1,2,3,4,5], y: [1,2,4,8,16]}]); }"
+        }
+      ]
+    };
+
     QUnit.module("new_module.js", {
-        before: function (assert) {
-            // setup before module, e.g.:
-            new_module.init();
+        before: async function (assert) {
+            // setup before module
+            await new_module.configure(test_config);
         },
         beforeEach: function (assert) {
             // setup before each test
@@ -40,11 +62,26 @@ var new_module_test_suite = function ($, new_module, QUnit) {
         }
     });
 
-    QUnit.test("hello_world", function (assert) {
-        assert.equal(new_module.hello(), "Hello, world!", "hello returns the correct string");
-        new_module.addressee = "you";
-        assert.equal(new_module.hello(), "Hello, you!", "changed adressee");
-        assert.notOk(new_module._hello, "_hello is private");
+    QUnit.test("_creators", function (assert) {
+        assert.equal(new_module._creators.length, 7, "seven creators");
+    });
+
+    QUnit.test("add_preview_container", function(assert) {
+        var entity = $("<div/>");
+        var container = $(new_module.add_preview_container(entity[0]));
+        assert.ok(container.hasClass(new_module._css_class_preview_container), `has class ${new_module._css_class_preview_container}`);
+        assert.ok(container.hasClass(new_module._css_class_preview_container_resolvable), `has class ${new_module._css_class_preview_container_resolvable}`);
+    });
+
+    QUnit.test("root_preview_handler", async function(assert) {
+        for (let name_suffix of ["fall-back", "error", "load-forever", "success"]) {
+            let name = "TestPreviewRecord-" + name_suffix;
+            let entity_xml = `<Response><Record name="${name}"><Parent name='TestPreviewRecordType'/></Record></Response>`;
+            let entity = (await transformation.transformEntities(str2xml(entity_xml)))[0];
+            assert.equal(getEntityName(entity), name);
+            new_module.root_preview_handler(entity[0]);
+        }
+
     });
 
-}($, new_module, QUnit);
\ No newline at end of file
+}($, new_module, QUnit);
-- 
GitLab