GUI -- Fixed warning about undefined panel, added topoPanel destroy, added unit tests for topoPanel.

Change-Id: Ic7ecc2771aa64bb0db19fe3b8005dc3932b237c5
diff --git a/web/gui/src/main/webapp/app/view/topo/topoPanel.js b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
index 2e50499..fe09f89 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoPanel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoPanel.js
@@ -44,9 +44,14 @@
     // Panel API
     function createTopoPanel(id, opts) {
         var p = ps.createPanel(id, opts),
+            pid = id,
             header, body, footer;
         p.classed(pCls, true);
 
+        function panel() {
+            return p;
+        }
+
         function hAppend(x) {
             return header.append(x);
         }
@@ -71,6 +76,10 @@
             footer = p.el().select('.footer');
         }
 
+        function destroy() {
+            ps.destroyPanel(pid);
+        }
+
         // fromTop is how many pixels from the top of the page the panel is
         // max is the max height of the panel in pixels
         //    only adjusts if the body content would be 10px or larger
@@ -83,10 +92,7 @@
                 $log.warn('adjustHeight: height from top of page not given');
                 return null;
             } else if (!body || !p) {
-                // if we have reached this function without setting a panel
-                // sometimes the d3 tick function calls detail panel's 'up'
-                // on reload when body isn't defined yet.
-                $log.warn('adjustHeight: panel is not defined');
+                $log.warn('adjustHeight: panel contents are not defined');
                 return null;
             }
 
@@ -119,8 +125,9 @@
         }
 
         return {
-            panel: p,
+            panel: panel,
             setup: setup,
+            destroy: destroy,
             appendHeader: hAppend,
             appendBody: bAppend,
             appendFooter: fAppend,
@@ -317,7 +324,7 @@
 
     function toggleSummary(x) {
         var kev = (x === 'keyev'),
-            on = kev ? !summary.panel.isVisible() : !!x,
+            on = kev ? !summary.panel().isVisible() : !!x,
             verb = on ? 'Show' : 'Hide';
 
         if (on) {
@@ -336,10 +343,10 @@
 
     function showSummaryPanel() {
         function _show() {
-            summary.panel.show();
+            summary.panel().show();
             summary.adjustHeight(64, 226);
         }
-        if (detail.panel.isVisible()) {
+        if (detail.panel().isVisible()) {
             detail.down(_show);
         } else {
             _show();
@@ -349,19 +356,20 @@
     function hideSummaryPanel() {
         // instruct server to stop sending summary data
         wss.sendEvent("cancelSummary");
-        summary.panel.hide(detail.up);
+        summary.panel().hide(detail.up);
     }
 
     function showDetailPanel() {
-        if (summary.panel.isVisible()) {
-            detail.down(detail.panel.show);
+        if (summary.panel().isVisible()) {
+            detail.down(detail.panel().show);
         } else {
-            detail.up(detail.panel.show);
+            detail.up(detail.panel().show);
         }
+        detail.adjustHeight(detail.ypos.current);
     }
 
     function hideDetailPanel() {
-        detail.panel.hide();
+        detail.panel().hide();
     }
 
     // ==========================
@@ -377,7 +385,7 @@
                 yp = d.ypos;
             if (yp.current !== y) {
                 yp.current = y;
-                d.panel.el().transition().duration(300)
+                d.panel().el().transition().duration(300)
                     .each('end', endCb)
                     .style('top', yp.current + 'px');
             } else {
@@ -385,15 +393,8 @@
             }
         };
 
-        // d.up is being called on the tick function for some reason
-        d.down = function (cb) {
-            d._move(d.ypos.down, cb);
-            detail.adjustHeight(d.ypos.current);
-        };
-        d.up = function (cb) {
-            d._move(d.ypos.up, cb);
-            detail.adjustHeight(d.ypos.current);
-        };
+        d.down = function (cb) { d._move(d.ypos.down, cb); };
+        d.up = function (cb) { d._move(d.ypos.up, cb); };
     }
 
     function toggleUseDetailsFlag(x) {
@@ -424,9 +425,11 @@
     }
 
     function destroyPanels() {
-        ps.destroyPanel(idSum);
-        ps.destroyPanel(idDet);
-        summary.panel = detail.panel = null;
+        summary.destroy();
+        summary = null;
+
+        detail.destroy();
+        detail = null;
         haveDetails = false;
     }
 
@@ -464,8 +467,8 @@
 
                 hideSummaryPanel: hideSummaryPanel,
 
-                detailVisible: function () { return detail.panel.isVisible(); },
-                summaryVisible: function () { return summary.panel.isVisible(); }
+                detailVisible: function () { return detail.panel().isVisible(); },
+                summaryVisible: function () { return summary.panel().isVisible(); }
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js
index bd1c172..21513d1 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js
@@ -18,19 +18,41 @@
  ONOS GUI -- Topo View -- Topo Panel Service - Unit Tests
  */
 describe('factory: view/topo/topoPanel.js', function() {
-    var $log, fs, tps, bns;
+    var $log, fs, tps, bns, ps, panelLayer;
+
+    var mockWindow = {
+        innerWidth: 300,
+        innerHeight: 100,
+        navigator: {
+            userAgent: 'defaultUA'
+        },
+        on: function () {},
+        addEventListener: function () {}
+    };
 
     beforeEach(module('ovTopo', 'onosUtil', 'onosLayer', 'ngRoute', 'onosNav',
         'onosWidget'));
 
+    beforeEach(function () {
+        module(function ($provide) {
+            $provide.value('$window', mockWindow);
+        });
+    });
+
     beforeEach(inject(function (_$log_, FnService,
-                                TopoPanelService, ButtonService) {
+                                TopoPanelService, ButtonService, PanelService) {
         $log = _$log_;
         fs = FnService;
         tps = TopoPanelService;
         bns = ButtonService;
+        ps = PanelService;
+        panelLayer = d3.select('body').append('div').attr('id', 'floatpanels');
     }));
 
+    afterEach(function () {
+        panelLayer.remove();
+    });
+
     it('should define TopoPanelService', function () {
         expect(tps).toBeDefined();
     });
@@ -39,20 +61,99 @@
         expect(fs.areFunctions(tps, [
             'initPanels',
             'destroyPanels',
+            'createTopoPanel',
+
             'showSummary',
             'toggleSummary',
+
             'toggleUseDetailsFlag',
             'displaySingle',
             'displayMulti',
-            'addAction',
             'displayLink',
             'displayNothing',
             'displaySomething',
+            'addAction',
+
             'hideSummaryPanel',
+
             'detailVisible',
             'summaryVisible'
         ])).toBeTruthy();
     });
 
+    // === topoPanel api ------------------
+
+    it('should define topoPanel api functions', function () {
+        var panel = tps.createTopoPanel('foo');
+        expect(fs.areFunctions(panel, [
+            'panel', 'setup', 'destroy',
+            'appendHeader', 'appendBody', 'appendFooter',
+            'adjustHeight'
+        ])).toBeTruthy();
+        panel.destroy();
+    });
+
+    it('should allow you to get panel', function () {
+        var panel = tps.createTopoPanel('foo');
+        expect(panel.panel()).toBeTruthy();
+        panel.destroy();
+    });
+
+    it('should set up panel', function () {
+        var p = tps.createTopoPanel('foo'),
+            h, b, f;
+        p.setup();
+        expect(p.panel().el().selectAll('div').size()).toBe(3);
+
+        h = p.panel().el().select('.header');
+        expect(h.empty()).toBe(false);
+        b = p.panel().el().select('.body');
+        expect(b.empty()).toBe(false);
+        f = p.panel().el().select('.footer');
+        expect(f.empty()).toBe(false);
+        p.destroy();
+    });
+
+    it('should destroy panel', function () {
+        spyOn(ps, 'destroyPanel').and.callThrough();
+        var p = tps.createTopoPanel('foo');
+        p.destroy();
+        expect(ps.destroyPanel).toHaveBeenCalledWith('foo');
+    });
+
+    it('should append to panel', function () {
+        var p = tps.createTopoPanel('foo');
+        p.setup();
+        p.appendHeader('div').attr('id', 'header-div');
+        expect(p.panel().el().select('#header-div').empty()).toBe(false);
+        p.appendBody('p').attr('id', 'body-paragraph');
+        expect(p.panel().el().select('#body-paragraph').empty()).toBe(false);
+        p.appendFooter('svg').attr('id', 'footer-svg');
+        expect(p.panel().el().select('#footer-svg').empty()).toBe(false);
+        p.destroy();
+    });
+
+    it('should warn if fromTop not given, adjustHeight', function () {
+        spyOn($log, 'warn');
+        var p = tps.createTopoPanel('foo');
+        p.adjustHeight();
+        expect($log.warn).toHaveBeenCalledWith(
+            'adjustHeight: height from top of page not given'
+        );
+        p.destroy();
+    });
+
+    it('should warn if panel is not setup/defined, adjustHeight', function () {
+        spyOn($log, 'warn');
+        var p = tps.createTopoPanel('foo');
+        p.adjustHeight(50);
+        expect($log.warn).toHaveBeenCalledWith(
+            'adjustHeight: panel contents are not defined'
+        );
+        p.destroy();
+    });
+
+    // TODO: test adjustHeight height adjustment
+
     // TODO: more tests...
 });