GUI -- reworking code to eliminate unnecessary 'ONOS' global.
 - re-package fn and keys factories into util module.

Change-Id: I3d9f50b9a91468140845e862aff3fdb518948774
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
new file mode 100644
index 0000000..6cf41a6
--- /dev/null
+++ b/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
@@ -0,0 +1,161 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Util -- General Purpose Functions - Unit Tests
+
+ @author Simon Hunt
+ */
+describe('factory: fw/lib/fn.js', function() {
+    var fs,
+        someFunction = function () {},
+        someArray = [1, 2, 3],
+        someObject = { foo: 'bar'},
+        someNumber = 42,
+        someString = 'xyyzy',
+        someDate = new Date(),
+        stringArray = ['foo', 'bar'];
+
+    beforeEach(module('onosUtil'));
+
+    beforeEach(inject(function (FnService) {
+        fs = FnService;
+    }));
+
+
+    // === Tests for isF()
+    it('isF(): null for undefined', function () {
+        expect(fs.isF(undefined)).toBeNull();
+    });
+    it('isF(): null for null', function () {
+        expect(fs.isF(null)).toBeNull();
+    });
+    it('isF(): the reference for function', function () {
+        expect(fs.isF(someFunction)).toBe(someFunction);
+    });
+    it('isF(): null for string', function () {
+        expect(fs.isF(someString)).toBeNull();
+    });
+    it('isF(): null for number', function () {
+        expect(fs.isF(someNumber)).toBeNull();
+    });
+    it('isF(): null for Date', function () {
+        expect(fs.isF(someDate)).toBeNull();
+    });
+    it('isF(): null for array', function () {
+        expect(fs.isF(someArray)).toBeNull();
+    });
+    it('isF(): null for object', function () {
+        expect(fs.isF(someObject)).toBeNull();
+    });
+
+
+    // === Tests for isA()
+    it('isA(): null for undefined', function () {
+        expect(fs.isA(undefined)).toBeNull();
+    });
+    it('isA(): null for null', function () {
+        expect(fs.isA(null)).toBeNull();
+    });
+    it('isA(): null for function', function () {
+        expect(fs.isA(someFunction)).toBeNull();
+    });
+    it('isA(): null for string', function () {
+        expect(fs.isA(someString)).toBeNull();
+    });
+    it('isA(): null for number', function () {
+        expect(fs.isA(someNumber)).toBeNull();
+    });
+    it('isA(): null for Date', function () {
+        expect(fs.isA(someDate)).toBeNull();
+    });
+    it('isA(): the reference for array', function () {
+        expect(fs.isA(someArray)).toBe(someArray);
+    });
+    it('isA(): null for object', function () {
+        expect(fs.isA(someObject)).toBeNull();
+    });
+
+
+    // === Tests for isS()
+    it('isS(): null for undefined', function () {
+        expect(fs.isS(undefined)).toBeNull();
+    });
+    it('isS(): null for null', function () {
+        expect(fs.isS(null)).toBeNull();
+    });
+    it('isS(): null for function', function () {
+        expect(fs.isS(someFunction)).toBeNull();
+    });
+    it('isS(): the reference for string', function () {
+        expect(fs.isS(someString)).toBe(someString);
+    });
+    it('isS(): null for number', function () {
+        expect(fs.isS(someNumber)).toBeNull();
+    });
+    it('isS(): null for Date', function () {
+        expect(fs.isS(someDate)).toBeNull();
+    });
+    it('isS(): null for array', function () {
+        expect(fs.isS(someArray)).toBeNull();
+    });
+    it('isS(): null for object', function () {
+        expect(fs.isS(someObject)).toBeNull();
+    });
+
+
+    // === Tests for isO()
+    it('isO(): null for undefined', function () {
+        expect(fs.isO(undefined)).toBeNull();
+    });
+    it('isO(): null for null', function () {
+        expect(fs.isO(null)).toBeNull();
+    });
+    it('isO(): null for function', function () {
+        expect(fs.isO(someFunction)).toBeNull();
+    });
+    it('isO(): null for string', function () {
+        expect(fs.isO(someString)).toBeNull();
+    });
+    it('isO(): null for number', function () {
+        expect(fs.isO(someNumber)).toBeNull();
+    });
+    it('isO(): null for Date', function () {
+        expect(fs.isO(someDate)).toBeNull();
+    });
+    it('isO(): null for array', function () {
+        expect(fs.isO(someArray)).toBeNull();
+    });
+    it('isO(): the reference for object', function () {
+        expect(fs.isO(someObject)).toBe(someObject);
+    });
+
+    // === Tests for contains()
+    it('contains(): false for improper args', function () {
+        expect(fs.contains()).toBeFalsy();
+    });
+    it('contains(): false for non-array', function () {
+        expect(fs.contains(null, 1)).toBeFalsy();
+    });
+    it('contains(): true for contained item', function () {
+        expect(fs.contains(someArray, 1)).toBeTruthy();
+        expect(fs.contains(stringArray, 'bar')).toBeTruthy();
+    });
+    it('contains(): false for non-contained item', function () {
+        expect(fs.contains(someArray, 109)).toBeFalsy();
+        expect(fs.contains(stringArray, 'zonko')).toBeFalsy();
+    });
+});
diff --git a/web/gui/src/main/webapp/tests/app/fw/util/keys-spec.js b/web/gui/src/main/webapp/tests/app/fw/util/keys-spec.js
new file mode 100644
index 0000000..a37a84b
--- /dev/null
+++ b/web/gui/src/main/webapp/tests/app/fw/util/keys-spec.js
@@ -0,0 +1,245 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Key Handler Service - Unit Tests
+
+ @author Simon Hunt
+ */
+describe('factory: fw/lib/keys.js', function() {
+    var $log, ks, fs,
+        d3Elem, elem, last;
+  
+
+    beforeEach(module('onosUtil'));
+
+    beforeEach(inject(function (_$log_, KeyService, FnService) {
+        $log = _$log_;
+        ks = KeyService;
+        fs = FnService;
+        d3Elem = d3.select('body').append('p').attr('id', 'ptest');
+        elem = d3Elem.node();
+        ks.installOn(d3Elem);
+        last = {
+            view: null,
+            key: null,
+            code: null,
+            ev: null
+        };
+    }));
+
+    afterEach(function () {
+        d3.select('#ptest').remove();
+    });
+
+    // Code to emulate key presses....
+    // NOTE: kinda messy, but it seems to get the job done.
+    function jsKeyDown(element, code) {
+        var ev = document.createEvent('KeyboardEvent');
+
+        // Chromium Hack
+        if (navigator.userAgent.toLowerCase().indexOf('chrome') > -1) {
+            Object.defineProperty(ev, 'keyCode', {
+                get: function () { return this.keyCodeVal; }
+            });
+            Object.defineProperty(ev, 'which', {
+                get: function () { return this.keyCodeVal; }
+            });
+        }
+
+        if (ev.initKeyboardEvent) {
+            ev.initKeyboardEvent('keydown', true, true, document.defaultView,
+                                false, false, false, false, code, code);
+        } else {
+            ev.initKeyEvent('keydown', true, true, document.defaultView,
+                            false, false, false, false, code, 0);
+        }
+
+        ev.keyCodeVal = code;
+
+        if (ev.keyCode !== code) {
+            console.warn("keyCode mismatch " + ev.keyCode +
+                        "(" + ev.which + ") -> "+ code);
+        }
+        element.dispatchEvent(ev);
+    }
+
+    // === Theme related tests
+    // TODO: fix these tests once we have ThemeService
+/*
+    it('should start in light theme', function () {
+        expect(ks.theme()).toEqual('light');
+    });
+    it('should toggle to dark theme', function () {
+        jsKeyDown(elem, 84); // 'T'
+        expect(ks.theme()).toEqual('dark');
+    });
+*/
+
+    // === Key binding related tests
+    it('should start with default key bindings', function () {
+        var state = ks.keyBindings(),
+            gk = state.globalKeys,
+            mk = state.maskedKeys,
+            vk = state.viewKeys,
+            vf = state.viewFunction;
+
+        expect(gk.length).toEqual(4);
+        ['backSlash', 'slash', 'esc', 'T'].forEach(function (k) {
+            expect(fs.contains(gk, k)).toBeTruthy();
+        });
+
+        expect(mk.length).toEqual(3);
+        ['backSlash', 'slash', 'T'].forEach(function (k) {
+            expect(fs.contains(mk, k)).toBeTruthy();
+        });
+
+        expect(vk.length).toEqual(0);
+        expect(vf).toBeFalsy();
+    });
+
+    function bindTestKeys(withDescs) {
+        var keys = ['A', '1', 'F5', 'equals'],
+            kb = {};
+
+        function cb(view, key, code, ev) {
+            last.view = view;
+            last.key = key;
+            last.code = code;
+            last.ev = ev;
+        }
+
+        function bind(k) {
+            return withDescs ? [cb, 'desc for key ' + k] : cb;
+        }
+
+        keys.forEach(function (k) {
+            kb[k] = bind(k);
+        });
+
+        ks.keyBindings(kb);
+    }
+
+    function verifyCall(key, code) {
+        // TODO: update expectation, when view tokens are implemented
+        expect(last.view).toEqual('NotYetAViewToken');
+        last.view = null;
+
+        expect(last.key).toEqual(key);
+        last.key = null;
+
+        expect(last.code).toEqual(code);
+        last.code = null;
+
+        expect(last.ev).toBeTruthy();
+        last.ev = null;
+    }
+
+    function verifyNoCall() {
+        expect(last.view).toBeNull();
+        expect(last.key).toBeNull();
+        expect(last.code).toBeNull();
+        expect(last.ev).toBeNull();
+    }
+
+    function verifyTestKeys() {
+        jsKeyDown(elem, 65); // 'A'
+        verifyCall('A', 65);
+        jsKeyDown(elem, 66); // 'B'
+        verifyNoCall();
+
+        jsKeyDown(elem, 49); // '1'
+        verifyCall('1', 49);
+        jsKeyDown(elem, 50); // '2'
+        verifyNoCall();
+
+        jsKeyDown(elem, 116); // 'F5'
+        verifyCall('F5', 116);
+        jsKeyDown(elem, 117); // 'F6'
+        verifyNoCall();
+
+        jsKeyDown(elem, 187); // 'equals'
+        verifyCall('equals', 187);
+        jsKeyDown(elem, 189); // 'dash'
+        verifyNoCall();
+
+        var vk = ks.keyBindings().viewKeys;
+
+        expect(vk.length).toEqual(4);
+        ['A', '1', 'F5', 'equals'].forEach(function (k) {
+            expect(fs.contains(vk, k)).toBeTruthy();
+        });
+
+        expect(ks.keyBindings().viewFunction).toBeFalsy();
+    }
+
+    it('should allow specific key bindings', function () {
+        bindTestKeys();
+        verifyTestKeys();
+    });
+
+    it('should allow specific key bindings with descriptions', function () {
+        bindTestKeys(true);
+        verifyTestKeys();
+    });
+
+    it('should warn about masked keys', function () {
+        var k = {'space': cb, 'T': cb},
+            count = 0;
+
+        function cb(token, key, code, ev) {
+            count++;
+            //console.debug('count = ' + count, token, key, code);
+        }
+
+        spyOn($log, 'warn');
+
+        ks.keyBindings(k);
+
+        expect($log.warn).toHaveBeenCalledWith('setKeyBindings(): Key "T" is reserved');
+
+        // the 'T' key should NOT invoke our callback
+        expect(count).toEqual(0);
+        jsKeyDown(elem, 84); // 'T'
+        expect(count).toEqual(0);
+
+        // but the 'space' key SHOULD invoke our callback
+        jsKeyDown(elem, 32); // 'space'
+        expect(count).toEqual(1);
+    });
+
+    // === Gesture notes related tests
+    it('should start with no notes', function () {
+        expect(ks.gestureNotes()).toEqual([]);
+    });
+
+    it('should allow us to add nodes', function () {
+        var notes = [
+            ['one', 'something about one'],
+            ['two', 'description of two']
+        ];
+        ks.gestureNotes(notes);
+
+        expect(ks.gestureNotes()).toEqual(notes);
+    });
+
+    it('should ignore non-arrays', function () {
+        ks.gestureNotes({foo:4});
+        expect(ks.gestureNotes()).toEqual([]);
+    });
+
+    // Consider adding test to ensure array contains 2-tuples of strings
+});
diff --git a/web/gui/src/main/webapp/tests/app/mast/mast-spec.js b/web/gui/src/main/webapp/tests/app/mast/mast-spec.js
new file mode 100644
index 0000000..bf8194e
--- /dev/null
+++ b/web/gui/src/main/webapp/tests/app/mast/mast-spec.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Masthead Controller - Unit Tests
+
+ @author Simon Hunt
+ */
+describe('Controller: MastCtrl', function () {
+    // instantiate the masthead module
+    beforeEach(module('onosMast'));
+
+    var $log, ctrl;
+
+    // we need an instance of the controller
+    beforeEach(inject(function(_$log_, $controller) {
+        $log = _$log_;
+        ctrl = $controller('MastCtrl');
+    }));
+
+    it('should start with no radio buttons', function () {
+        expect(ctrl.radio).toBeNull();
+    });
+});
\ No newline at end of file
diff --git a/web/gui/src/main/webapp/tests/app/onos-spec.js b/web/gui/src/main/webapp/tests/app/onos-spec.js
new file mode 100644
index 0000000..e00fed4
--- /dev/null
+++ b/web/gui/src/main/webapp/tests/app/onos-spec.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ ONOS GUI -- Main App Controller - Unit Tests
+
+ @author Simon Hunt
+ */
+describe('Controller: OnosCtrl', function () {
+    // instantiate the main module
+    beforeEach(module('onosApp'));
+
+    var $log, ctrl;
+
+    // we need an instance of the controller
+    beforeEach(inject(function(_$log_, $controller) {
+        $log = _$log_;
+        ctrl = $controller('OnosCtrl');
+    }));
+
+    it('should report version 1.1.0', function () {
+        expect(ctrl.version).toEqual('1.1.0');
+    });
+});
\ No newline at end of file