ONOS-1722 - GUI -- QuickHelp unit tests written, minor other user input precautions added.

Change-Id: Ifeec1a014bc0dd72026295f2331c8fe5416330fd
diff --git a/web/gui/src/main/webapp/app/fw/layer/quickhelp.js b/web/gui/src/main/webapp/app/fw/layer/quickhelp.js
index d5e8c0d..a25cf68 100644
--- a/web/gui/src/main/webapp/app/fw/layer/quickhelp.js
+++ b/web/gui/src/main/webapp/app/fw/layer/quickhelp.js
@@ -59,6 +59,11 @@
         downArrow: 'D-arrow'
     };
 
+    // list of needed bindings to use in aggregateData
+    var neededBindings = [
+        'globalKeys', 'globalFormat', 'viewKeys', 'viewGestures'
+    ];
+
     // ===========================================
     // === Function Definitions ===
 
@@ -315,6 +320,22 @@
             .remove();
     }
 
+    function goodBindings(bindings) {
+        var warnPrefix = 'Quickhelp Service: showQuickHelp(), ';
+        if (!bindings || !fs.isO(bindings) || fs.isEmptyObject(bindings)) {
+            $log.warn(warnPrefix + 'invalid bindings object');
+            return false;
+        }
+        if (!(neededBindings.every(function (key) { return key in bindings; }))) {
+            $log.warn(
+                warnPrefix +
+                'needed bindings for help panel not provided:',
+                neededBindings
+            );
+            return false
+        }
+        return true;
+    }
 
     // ===========================================
     // === Module Definition ===
@@ -329,12 +350,15 @@
             sus = _sus_;
 
             function initQuickHelp(opts) {
-                settings = angular.extend({}, defaultSettings, opts);
+                settings = angular.extend({}, defaultSettings, fs.isO(opts));
                 qhdiv = d3.select('#quickhelp');
             }
 
             function showQuickHelp(bindings) {
                 svg = qhdiv.select('svg');
+                if (!goodBindings(bindings)) {
+                    return null;
+                }
                 if (svg.empty()) {
                     addSvg();
                     popBind(bindings);
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 2415282..ed1dbc6 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -178,8 +178,23 @@
 
     // --- Toolbar Functions ---------------------------------------------
 
+    function notValid(what) {
+        $log.warn('Topo.js getActionEntry(): Not a valid ' + what);
+    }
     function getActionEntry(key) {
-        var entry = actionMap[key];
+        var entry;
+
+        if (!key) {
+            notValid('key');
+            return null;
+        }
+
+        entry = actionMap[key];
+
+        if (!entry) {
+            notValid('actionMap entry');
+            return null;
+        }
         return fs.isA(entry) || [entry, ''];
     }
 
diff --git a/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js b/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js
index 68ac923..0f473b4 100644
--- a/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js
@@ -18,7 +18,26 @@
  ONOS GUI -- Layer -- Flash Service - Unit Tests
  */
 describe('factory: fw/layer/quickhelp.js', function () {
-    var $log, fs, qhs, d3Elem;
+    var $log, fs, qhs, d3Elem,
+        fade = 500,
+        noop = function () {},
+        mockBindings = {
+            globalKeys: {
+                slash: [noop, 'Show / hide Quick Help'],
+                T: [noop, 'Toggle Theme']
+            },
+            globalFormat: ['slash', 'T'],
+            viewKeys: {
+                H: [noop, 'Show / hide hosts'],
+                I: [noop, 'Toggle instances panel']
+            },
+            viewGestures: []
+        };
+
+    // list of needed bindings to use in aggregateData
+    var neededBindings = [
+        'globalKeys', 'globalFormat', 'viewKeys', 'viewGestures'
+    ];
 
     beforeEach(module('onosUtil', 'onosSvg', 'onosLayer'));
 
@@ -26,13 +45,15 @@
         $log = _$log_;
         fs = FnService;
         qhs = QuickHelpService;
+
         jasmine.clock().install();
-        d3Elem = d3.select('body').append('div').attr('id', 'myqhdiv');
+        d3Elem = d3.select('body').append('div').attr('id', 'quickhelp');
+        qhs.initQuickHelp();
     }));
 
     afterEach(function () {
         jasmine.clock().uninstall();
-        d3.select('#myqhdiv').remove();
+        d3.select('#quickhelp').remove();
     });
 
     function helpItemSelection() {
@@ -53,22 +74,141 @@
         expect(helpItemSelection().size()).toBe(0);
     });
 
-    // TODO: check that the help stuff appears
-/*
-    it('should show help items', function () {
-        var item, rect, text;
-        flash.flash('foo');
-        //jasmine.clock().tick(101);
-        setTimeout(function () {
-            item = flashItemSelection();
-            expect(item.size()).toEqual(1);
-            expect(item.classed('flashItem')).toBeTruthy();
-            expect(item.select('rect').size()).toEqual(1);
-            text = item.select('text');
-            expect(text.size()).toEqual(1);
-            expect(text.text()).toEqual('foo');
-        }, 100);
+    // === showQuickHelp
+
+    it('should warn if bad bindings are provided', function () {
+        var warning =
+            'Quickhelp Service: showQuickHelp(), invalid bindings object';
+        spyOn($log, 'warn');
+
+        expect(qhs.showQuickHelp()).toBeNull();
+        expect($log.warn).toHaveBeenCalledWith(warning);
+
+        expect(qhs.showQuickHelp({})).toBeNull();
+        expect($log.warn).toHaveBeenCalledWith(warning);
+
+        expect(qhs.showQuickHelp([1, 2, 3])).toBeNull();
+        expect($log.warn).toHaveBeenCalledWith(warning);
     });
-*/
+
+    it('should warn if not all needed bindings are provided', function () {
+        var warning =
+            'Quickhelp Service: showQuickHelp(),' +
+            ' needed bindings for help panel not provided:';
+        spyOn($log, 'warn');
+
+        expect(qhs.showQuickHelp({
+            foo: 'foo', bar: 'bar'
+        })).toBeNull();
+        expect($log.warn).toHaveBeenCalledWith(warning, neededBindings);
+
+        expect(qhs.showQuickHelp({
+            globalKeys: {}
+        })).toBeNull();
+        expect($log.warn).toHaveBeenCalledWith(warning, neededBindings);
+
+        expect(qhs.showQuickHelp({
+            globalKeys: {},
+            globalFormat: {},
+            viewKeys: {}
+        })).toBeNull();
+        expect($log.warn).toHaveBeenCalledWith(warning, neededBindings);
+    });
+
+    it('should not warn if bindings are provided', function () {
+        spyOn($log, 'warn');
+        expect(qhs.showQuickHelp(mockBindings)).toBe(undefined);
+        expect($log.warn).not.toHaveBeenCalled();
+    });
+
+    it('should append an svg', function () {
+        var svg = d3Elem.select('svg');
+        expect(d3Elem.empty()).toBe(false);
+        expect(svg.empty()).toBe(true);
+
+        qhs.showQuickHelp(mockBindings);
+
+        svg = d3Elem.select('svg');
+        expect(svg.empty()).toBe(false);
+        expect(svg.attr('width')).toBe('100%');
+        expect(svg.attr('height')).toBe('80%');
+        expect(svg.attr('viewBox')).toBe('-200 0 400 400');
+    });
+
+    it('should create the quick help panel', function () {
+        var helpItems, g, rect, text, rows;
+        qhs.showQuickHelp(mockBindings);
+
+        helpItems = helpItemSelection();
+        expect(helpItems.size()).toBe(1);
+
+        g = d3.select('g.help');
+        expect(g.attr('opacity')).toBe('0');
+
+        rect = g.select('rect');
+        expect(rect.attr('rx')).toBe('8');
+
+        text = g.select('text');
+        expect(text.text()).toBe('Quick Help');
+        expect(text.classed('title')).toBe(true);
+        expect(text.attr('dy')).toBe('1.2em');
+        expect(text.attr('transform')).toBeTruthy();
+
+        rows = g.select('g');
+        expect(rows.empty()).toBe(false);
+
+        jasmine.clock().tick(fade + 1);
+        setTimeout(function () {
+            expect(g.attr('opacity')).toBe('1');
+        }, fade);
+
+        // TODO: test aggregate data helper function
+    });
+
+    it('should show panel with custom fade time', function () {
+        var g,
+            ctmFade = 200;
+        qhs.initQuickHelp({ fade: ctmFade });
+        qhs.showQuickHelp(mockBindings);
+
+        g = d3.select('g.help');
+        expect(g.attr('opacity')).toBe('0');
+
+        jasmine.clock().tick(ctmFade + 1);
+        setTimeout(function () {
+            expect(g.attr('opacity')).toBe('1');
+        }, ctmFade);
+    });
+
+    // === hideQuickHelp
+
+    it('should hide quick help if svg exists', function () {
+        var svg;
+
+        expect(qhs.hideQuickHelp()).toBe(false);
+
+        svg = d3.select('#quickhelp')
+            .append('svg');
+        svg.append('g')
+            .classed('help', true)
+            .attr('opacity', 1);
+
+        expect(qhs.hideQuickHelp()).toBe(true);
+
+        jasmine.clock().tick(fade + 1);
+        setTimeout(function () {
+            expect(svg.select('g.help').attr('opacity')).toBe('0');
+        }, fade);
+
+        jasmine.clock().tick(20);
+        setTimeout(function () {
+            expect(svg.empty()).toBe(true);
+        }, fade + 20);
+    });
+
+    it('should not hide quick help if svg does not exist', function () {
+        expect(qhs.hideQuickHelp()).toBe(false);
+    });
+
 });
 
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js
index 63d2f23..eedc476 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js
@@ -18,7 +18,7 @@
  ONOS GUI -- Topo View -- Topo Toolbar Service - Unit Tests
  */
 describe('factory: view/topo/topoToolbar.js', function() {
-    var $log, fs, tts, prefs, ps,
+    var $log, fs, ttbs, prefs, ps,
         d3Elem;
 
     beforeEach(module('ovTopo', 'onosUtil', 'onosLayer', 'ngRoute', 'onosNav',
@@ -28,7 +28,7 @@
                                 TopoToolbarService, PanelService, PrefsService) {
         $log = _$log_;
         fs = FnService;
-        tts = TopoToolbarService;
+        ttbs = TopoToolbarService;
         prefs = PrefsService;
         ps = PanelService;
         d3Elem = d3.select('body').append('div').attr('id', 'floatpanels');
@@ -36,35 +36,17 @@
     }));
 
     it('should define TopoToolbarService', function () {
-        expect(tts).toBeDefined();
+        expect(ttbs).toBeDefined();
     });
 
     it('should define api functions', function () {
-        expect(fs.areFunctions(tts, [
+        expect(fs.areFunctions(ttbs, [
             'init', 'createToolbar', 'destroyToolbar',
             'keyListener', 'toggleToolbar'
         ])).toBeTruthy();
     });
 
-    function verifyFirstRow() {
-
-    }
-
-    function verifySecondRow() {
-
-    }
-
-    function verifyThirdRow() {
-
-    }
-
-    it('should create a toolbar', function () {
-        //var toolbar = tts.createToolbar('foo');
-        //expect(toolbar).toBeTruthy();
-        //expect(d3.select('#toolbar-foo').empty()).toBe(false);
-        //verifyFirstRow();
-        //verifySecondRow();
-        //verifyThirdRow();
-    });
+    // NOTE: topoToolbar relies too much on topo's closure variables
+    // to adequately test it
 
 });
\ No newline at end of file