Merge "Fix plugin test suite"
diff --git a/dev/js/spec/pluginSpec.js b/dev/js/spec/pluginSpec.js
index 50c4982..b11dc7a 100644
--- a/dev/js/spec/pluginSpec.js
+++ b/dev/js/spec/pluginSpec.js
@@ -1,4 +1,4 @@
-define(['plugin/server','plugin/widget'], function (pluginServerClass, widgetClass) {
+define(['plugin/server','plugin/widget','panel'], function (pluginServerClass, widgetClass, panelClass) {
 
   describe('KorAP.Plugin.Server', function () {
 
@@ -8,17 +8,48 @@
       manager.destroy();
     });
 
+
     it('should add a widget', function () {
       var manager = pluginServerClass.create();
-      var div = document.createElement("div");
-      var id = manager.addWidget(div, 'about:blank');
+      var panel = panelClass.create();
+      var id = manager.addWidget(panel, 'Example 1', 'about:blank');
       expect(id).toMatch(/^id-/);
-      expect(div.firstChild.classList.contains('widget')).toBeTruthy();
-      expect(div.firstChild.firstChild.tagName).toEqual("IFRAME");
+
+      var panelE = panel.element();
+      var widgetE = panelE.firstChild.firstChild;
+      expect(widgetE.classList.contains('widget')).toBeTruthy();
+      expect(widgetE.firstChild.tagName).toEqual("IFRAME");
+      var iframe = widgetE.firstChild;
+      expect(iframe.getAttribute("src")).toEqual("about:blank");
+
+      expect(widgetE.lastChild.firstChild.textContent).toEqual("Close");
+      expect(widgetE.lastChild.lastChild.textContent).toEqual("Example 1");
+
       manager.destroy();
     });
 
-    it('should fail on invalid registries', function () {
+    it('should close a widget', function () {
+      var manager = pluginServerClass.create();
+      var panel = panelClass.create();
+      var id = manager.addWidget(panel, 'Example 2', 'about:blank');
+      expect(id).toMatch(/^id-/);
+
+      var panelE = panel.element();
+      var widgetE = panelE.firstChild.firstChild;
+      expect(widgetE.classList.contains('widget')).toBeTruthy();
+
+      expect(panelE.getElementsByClassName('view').length).toEqual(1);
+
+      var widget = manager.widget(id);
+      widget.close();
+
+      expect(panelE.getElementsByClassName('view').length).toEqual(0);
+
+      manager.destroy();
+    });
+
+    
+    it('should fail on invalid registrations', function () {
       var manager = pluginServerClass.create();
 
       expect(
@@ -40,7 +71,6 @@
           }]
         })}
       ).toThrow(new Error("Panel for plugin is invalid"));
-
     });
   });
 
diff --git a/dev/js/src/plugin/server.js b/dev/js/src/plugin/server.js
index 934f6f3..1afc747 100644
--- a/dev/js/src/plugin/server.js
+++ b/dev/js/src/plugin/server.js
@@ -149,17 +149,21 @@
 
 
     /**
-     * Get named button group
+     * Get named button group - better rename to "action"
      */
     buttonGroup : function (name) {
       return buttons[name];
     },
     
     /**
-     * Open a new widget in a certaoin panel
+     * Open a new widget view in a certain panel and return
+     * the id.
      */
     addWidget : function (panel, name, src) {
 
+      if (!src)
+        return;
+
       // Is it the first widget?
       if (!this._listener) {
 
@@ -189,12 +193,23 @@
       widgets[id] = widget;
       limits[id] = maxMessages;
 
+      widget._mgr = this;
+
       // Add widget to panel
       panel.add(widget);
 
       return id;
     },
 
+
+    /**
+     * Get widget by identifier
+     */
+    widget : function (id) {
+      return widgets[id];
+    },
+
+    
     // Receive a call from an embedded iframe.
     // The handling needs to be very careful,
     // as this can easily become a security nightmare.
@@ -231,7 +246,10 @@
 
         // TODO:
         //   Potentially kill the whole plugin!
-        this.closeWidget(widget);
+
+        // This removes all connections before closing the widget 
+        this._closeWidget(widget.id);
+        widget.close();
         return;
       };
 
@@ -250,10 +268,9 @@
     },
 
     // Close the widget
-    closeWidget : function (widget) {
-      delete limits[widget.id];
-      delete widgets[widget.id];
-      widget.shutdown();
+    _closeWidget : function (id) {
+      delete limits[id];
+      delete widgets[id];
 
       // Remove listeners in case no widget
       // is available any longer
@@ -277,6 +294,9 @@
     // Destructor, just for testing scenarios
     destroy : function () {
       limits = {};
+      for (let w in widgets) {
+        widgets[w].close();
+      };
       widgets = {};
       this._removeListener();
     }
diff --git a/dev/js/src/plugin/widget.js b/dev/js/src/plugin/widget.js
index 17c1f5e..dbdeee9 100644
--- a/dev/js/src/plugin/widget.js
+++ b/dev/js/src/plugin/widget.js
@@ -66,9 +66,13 @@
       this.show().style.height = data.height + 'px';
     },
 
-    // Shutdown suspicious iframe
-    shutdown : function () {
-      this.element().parentNode.removeChild(this.element());
+
+    // On closing the widget view
+    onClose : function () {
+      if (this._mgr) {
+        this._mgr._closeWidget(this._id);
+        this._mgr = undefined;
+      };
     }
   }
 });
diff --git a/dev/js/src/view.js b/dev/js/src/view.js
index aabad29..2f35c9e 100644
--- a/dev/js/src/view.js
+++ b/dev/js/src/view.js
@@ -80,7 +80,9 @@
      */
     close : function () {
       var e = this.element();
-      e.parentNode.removeChild(e);
+      if (e.parentNode) {
+        e.parentNode.removeChild(e);
+      };
       this.panel.delView(this);
       this._shown = false;
       if (this.onClose)