summaryrefslogtreecommitdiffstats
path: root/framework/src/onos/web/gui/src/main/webapp/tests
diff options
context:
space:
mode:
authorAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
committerAshlee Young <ashlee@onosfw.com>2015-09-09 22:15:21 -0700
commit13d05bc8458758ee39cb829098241e89616717ee (patch)
tree22a4d1ce65f15952f07a3df5af4b462b4697cb3a /framework/src/onos/web/gui/src/main/webapp/tests
parent6139282e1e93c2322076de4b91b1c85d0bc4a8b3 (diff)
ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60
Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd
Diffstat (limited to 'framework/src/onos/web/gui/src/main/webapp/tests')
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/README.txt31
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/flash-spec.js72
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/panel-spec.js189
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js214
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/veil-spec.js45
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js37
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/nav/nav-spec.js165
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/rest-spec.js97
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js89
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/websocket-spec.js268
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js78
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/geodata-spec.js159
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js425
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/icon-spec.js106
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js87
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js237
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js152
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js446
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/keys-spec.js278
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js60
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/random-spec.js110
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js162
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/button-spec.js300
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js340
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js95
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/toolbar-spec.js180
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tooltip-spec.js79
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/onos-spec.js35
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/device-spec.js38
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/fakeData.json88
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoEvent-spec.js45
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js70
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js53
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoInst-spec.js45
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js414
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js45
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js159
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js51
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js52
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js47
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/e2e/README.txt2
-rw-r--r--framework/src/onos/web/gui/src/main/webapp/tests/karma.conf.js90
42 files changed, 5735 insertions, 0 deletions
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/README.txt b/framework/src/onos/web/gui/src/main/webapp/tests/README.txt
new file mode 100644
index 00000000..213b4841
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/README.txt
@@ -0,0 +1,31 @@
+####
+# Unit and integration tests for code under the /app directory
+####
+
+To run these tests, karma, node.js etc needs to be installed in the
+build environment.
+
+From the karma installation directory, execute the following:
+
+ $ karma start {_path_to_}/src/main/webapp/tests/karma.conf.js
+
+This will launch and capture a browser, install and run the unit tests.
+
+The configuration is currently set to re-run the tests every time a
+file change is detected, (i.e. each time a source file is saved).
+
+----------------------------------------------------------------------
+Useful Notes
+============
+
+Set a 'breakpoint' with the debugger command:
+
+ it('should define four functions', function () {
+ debugger;
+
+ expect(fs.isF(gs.init)).toBeTruthy();
+ // ...
+ });
+
+Open Developer Tools in the captured Chrome browser, and reload the page.
+The debugger will break at the given point, allowing you to inspect context.
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/flash-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/flash-spec.js
new file mode 100644
index 00000000..a17f9e78
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/flash-spec.js
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2015 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 -- Layer -- Flash Service - Unit Tests
+ */
+describe('factory: fw/layer/flash.js', function () {
+ var $log, $timeout, fs, flash, d3Elem;
+
+ beforeEach(module('onosLayer'));
+
+ beforeEach(inject(function (_$log_, _$timeout_, FnService, FlashService) {
+ $log = _$log_;
+ $timeout = _$timeout_;
+ fs = FnService;
+ flash = FlashService;
+ jasmine.clock().install();
+ d3Elem = d3.select('body').append('div').attr('id', 'myflashdiv');
+ flash.initFlash();
+ }));
+
+ afterEach(function () {
+ jasmine.clock().uninstall();
+ d3.select('#myflashdiv').remove();
+ });
+
+ function flashItemSelection() {
+ return d3Elem.selectAll('.flashItem');
+ }
+
+ it('should define FlashService', function () {
+ expect(flash).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(flash, [
+ 'initFlash', 'flash', 'enable'
+ ])).toBe(true);
+ });
+
+ it('should have no items to start', function () {
+ expect(flashItemSelection().size()).toBe(0);
+ });
+
+ it('should flash the message Foo', 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);
+ });
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/panel-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/panel-spec.js
new file mode 100644
index 00000000..24ed9900
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/panel-spec.js
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2015 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 -- Layer -- Panel Service - Unit Tests
+ */
+describe('factory: fw/layer/panel.js', function () {
+ var $log, $timeout, fs, ps, d3Elem;
+
+ beforeEach(module('onosLayer'));
+
+ beforeEach(inject(function (_$log_, _$timeout_, FnService, PanelService) {
+ $log = _$log_;
+ $timeout = _$timeout_;
+ fs = FnService;
+ ps = PanelService;
+ d3Elem = d3.select('body').append('div').attr('id', 'floatpanels');
+ ps.init();
+ }));
+
+ afterEach(function () {
+ d3.select('#floatpanels').remove();
+ ps.init();
+ });
+
+ function floatPanelSelection() {
+ return d3Elem.selectAll('.floatpanel');
+ }
+
+ it('should define PanelService', function () {
+ expect(ps).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ps, [
+ 'init', 'createPanel', 'destroyPanel'
+ ])).toBeTruthy();
+ });
+
+ it('should have no panels to start', function () {
+ expect(floatPanelSelection().size()).toBe(0);
+ });
+
+ it('should log a warning if no ID is given', function () {
+ spyOn($log, 'warn');
+ var p = ps.createPanel();
+ expect(p).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('createPanel: no ID given');
+ expect(floatPanelSelection().size()).toBe(0);
+ });
+
+ it('should create a default panel', function () {
+ spyOn($log, 'warn');
+ spyOn($log, 'debug');
+ var p = ps.createPanel('foo');
+ expect(p).not.toBeNull();
+ expect($log.warn).not.toHaveBeenCalled();
+ expect(floatPanelSelection().size()).toBe(1);
+ expect($log.debug).toHaveBeenCalledWith('creating panel:', 'foo', {
+ edge: 'right',
+ width: 200,
+ margin: 20,
+ hideMargin: 20,
+ xtnTime: 750,
+ fade: true
+ });
+
+ // check basic properties
+ expect(p.width()).toEqual(200);
+ expect(p.isVisible()).toBeFalsy();
+
+ var el = floatPanelSelection();
+ expect(el.style('width')).toEqual('200px');
+ });
+
+ it('should provide an api of panel functions', function () {
+ var p = ps.createPanel('foo');
+ expect(fs.areFunctions(p, [
+ 'show', 'hide', 'toggle', 'empty', 'append',
+ 'width', 'height', 'isVisible', 'classed', 'el'
+ ])).toBeTruthy();
+ });
+
+ it('should complain when a duplicate ID is used', function () {
+ spyOn($log, 'warn');
+ var p = ps.createPanel('foo');
+ expect(p).not.toBeNull();
+ expect($log.warn).not.toHaveBeenCalled();
+ expect(floatPanelSelection().size()).toBe(1);
+
+ var dup = ps.createPanel('foo');
+ expect(dup).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('Panel with ID "foo" already exists');
+ expect(floatPanelSelection().size()).toBe(1);
+ });
+
+ it('should note when there is no panel to destroy', function () {
+ spyOn($log, 'debug');
+ ps.destroyPanel('bar');
+ expect($log.debug).toHaveBeenCalledWith('no panel to destroy:', 'bar');
+ });
+
+ it('should destroy the panel', function () {
+ spyOn($log, 'debug');
+ var p = ps.createPanel('foo');
+ expect(floatPanelSelection().size()).toBe(1);
+
+ ps.destroyPanel('foo');
+ expect($log.debug).toHaveBeenCalledWith('destroying panel:', 'foo');
+ expect(floatPanelSelection().size()).toBe(0);
+ });
+
+ it('should allow alternate settings to be given', function () {
+ spyOn($log, 'debug');
+ var p = ps.createPanel('foo', { width: 250, edge: 'left' });
+ expect($log.debug).toHaveBeenCalledWith('creating panel:', 'foo', {
+ edge: 'left',
+ width: 250,
+ margin: 20,
+ hideMargin: 20,
+ xtnTime: 750,
+ fade: true
+ });
+ });
+
+ it('should show and hide the panel', function () {
+ var p = ps.createPanel('foo', {xtnTime:0});
+ expect(p.isVisible()).toBeFalsy();
+
+ p.show();
+ expect(p.isVisible()).toBeTruthy();
+
+ p.hide();
+ expect(p.isVisible()).toBeFalsy();
+ });
+
+ it('should append content to the panel', function () {
+ var p = ps.createPanel('foo');
+ var span = p.append('span').attr('id', 'thisIsMySpan');
+
+ expect(floatPanelSelection().selectAll('span').attr('id'))
+ .toEqual('thisIsMySpan');
+ });
+
+ it('should remove content on empty', function () {
+ var p = ps.createPanel('voop');
+ p.append('span');
+ p.append('span');
+ p.append('span');
+ expect(floatPanelSelection().selectAll('span').size()).toEqual(3);
+
+ p.empty();
+ expect(floatPanelSelection().selectAll('span').size()).toEqual(0);
+ expect(floatPanelSelection().html()).toEqual('');
+ });
+
+ it('should allow programmatic setting of width', function () {
+ var p = ps.createPanel('whatcha', {width:234});
+ expect(floatPanelSelection().style('width')).toEqual('234px');
+ expect(p.width()).toEqual(234);
+
+ p.width(345);
+ expect(floatPanelSelection().style('width')).toEqual('345px');
+ expect(p.width()).toEqual(345);
+ });
+
+ it('should allow programmatic setting of height', function () {
+ var p = ps.createPanel('ciao', {height:50});
+ expect(floatPanelSelection().style('height')).toEqual('50px');
+ expect(p.height()).toEqual(50);
+
+ p.height(100);
+ expect(floatPanelSelection().style('height')).toEqual('100px');
+ expect(p.height()).toEqual(100);
+ });
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js
new file mode 100644
index 00000000..0f473b42
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js
@@ -0,0 +1,214 @@
+/*
+ * Copyright 2015 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 -- Layer -- Flash Service - Unit Tests
+ */
+describe('factory: fw/layer/quickhelp.js', function () {
+ 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'));
+
+ beforeEach(inject(function (_$log_, FnService, QuickHelpService) {
+ $log = _$log_;
+ fs = FnService;
+ qhs = QuickHelpService;
+
+ jasmine.clock().install();
+ d3Elem = d3.select('body').append('div').attr('id', 'quickhelp');
+ qhs.initQuickHelp();
+ }));
+
+ afterEach(function () {
+ jasmine.clock().uninstall();
+ d3.select('#quickhelp').remove();
+ });
+
+ function helpItemSelection() {
+ return d3Elem.selectAll('.help');
+ }
+
+ it('should define QuickHelpService', function () {
+ expect(qhs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(qhs, [
+ 'initQuickHelp', 'showQuickHelp', 'hideQuickHelp'
+ ])).toBeTruthy();
+ });
+
+ it('should have no items to start', function () {
+ expect(helpItemSelection().size()).toBe(0);
+ });
+
+ // === 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/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/veil-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/veil-spec.js
new file mode 100644
index 00000000..155ebaab
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/veil-spec.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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 -- Layer -- Veil Service - Unit Tests
+ */
+
+describe('factory: fw/layer/veil.js', function () {
+ var $log, $route, vs, fs, ks, gs;
+
+ beforeEach(module('onosLayer', 'onosNav', 'onosSvg', 'ngRoute'));
+
+ beforeEach(inject(function (_$log_, _$route_, VeilService, FnService,
+ KeyService, GlyphService) {
+ $log = _$log_;
+ $route = _$route_;
+ vs = VeilService;
+ fs = FnService;
+ ks = KeyService;
+ gs = GlyphService;
+ }));
+
+ it('should define VeilService', function () {
+ expect(vs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(vs, [
+ 'init', 'show', 'hide', 'lostServer'
+ ])).toBeTruthy();
+ });
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js
new file mode 100644
index 00000000..26ccef8a
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2014,2015 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
+ */
+describe('Controller: MastCtrl', function () {
+ // instantiate the masthead module
+ beforeEach(module('onosMast', 'onosUtil'));
+
+ var $log, ctrl, ms, fs;
+
+ // we need an instance of the controller
+ beforeEach(inject(function(_$log_, $controller, MastService, FnService) {
+ $log = _$log_;
+ ctrl = $controller('MastCtrl');
+ ms = MastService;
+ fs = FnService;
+ }));
+
+ it('should declare height to be 36', function () {
+ expect(ms.mastHeight()).toBe(36);
+ })
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/nav/nav-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/nav/nav-spec.js
new file mode 100644
index 00000000..d14d5147
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/nav/nav-spec.js
@@ -0,0 +1,165 @@
+/*
+ * Copyright 2014,2015 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 -- Theme Service - Unit Tests
+ */
+describe('factory: fw/nav/nav.js', function() {
+ var $log, $location, $window, ns, fs;
+ var d3Elem;
+
+ beforeEach(module('onosNav', 'onosUtil'));
+
+ var mockWindow = {
+ location: {
+ href: 'http://server/#/mock/url'
+ }
+ };
+
+ beforeEach(function () {
+ module(function ($provide) {
+ $provide.value('$window', mockWindow);
+ });
+ });
+
+ beforeEach(inject(function (_$log_, _$location_, _$window_,
+ NavService, FnService) {
+ $log = _$log_;
+ $location = _$location_;
+ $window = _$window_;
+ ns = NavService;
+ fs = FnService;
+ d3Elem = d3.select('body').append('div').attr('id', 'nav');
+ ns.hideNav();
+ }));
+
+ afterEach(function () {
+ d3.select('#nav').remove();
+ });
+
+ it('should define NavService', function () {
+ expect(ns).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ns, [
+ 'showNav', 'hideNav', 'toggleNav', 'hideIfShown', 'navTo'
+ ])).toBeTruthy();
+ });
+
+ function checkHidden(b) {
+ var what = b ? 'hidden' : 'visible';
+ expect(d3.select('#nav').style('visibility')).toEqual(what);
+ }
+
+ it('should start hidden', function () {
+ checkHidden(true);
+ });
+
+ it('should be shown then hidden', function () {
+ ns.showNav();
+ checkHidden(false);
+ ns.hideNav();
+ checkHidden(true);
+ });
+
+ it('should toggle hidden', function () {
+ ns.toggleNav();
+ checkHidden(false);
+ ns.toggleNav();
+ checkHidden(true);
+ });
+
+ it('should show idempotently', function () {
+ checkHidden(true);
+ ns.showNav();
+ checkHidden(false);
+ ns.showNav();
+ checkHidden(false);
+ });
+
+ it('should hide idempotently', function () {
+ checkHidden(true);
+ ns.hideNav();
+ checkHidden(true);
+ });
+
+ it('should be a noop if already hidden', function () {
+ checkHidden(true);
+ expect(ns.hideIfShown()).toBe(false);
+ checkHidden(true);
+ });
+
+ it('should hide if shown', function () {
+ ns.showNav();
+ checkHidden(false);
+ expect(ns.hideIfShown()).toBe(true);
+ checkHidden(true);
+ });
+
+ it('should take correct navTo parameters', function () {
+ spyOn($log, 'warn');
+
+ ns.navTo('foo');
+ expect($log.warn).not.toHaveBeenCalled();
+
+ ns.navTo('bar', { q1: 'thing', q2: 'thing2' });
+ expect($log.warn).not.toHaveBeenCalled();
+
+ });
+
+ it('should check navTo parameter warnings', function () {
+ spyOn($log, 'warn');
+
+ expect(ns.navTo()).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('Not a valid navigation path');
+
+ ns.navTo('baz', [1, 2, 3]);
+ expect($log.warn).toHaveBeenCalledWith(
+ 'Query params not an object', [1, 2, 3]
+ );
+
+ ns.navTo('zoom', 'not a query param');
+ expect($log.warn).toHaveBeenCalledWith(
+ 'Query params not an object', 'not a query param'
+ );
+ });
+
+ it('should verify where the window is navigating', function () {
+ ns.navTo('foo');
+ expect($window.location.href).toBe('http://server/#/foo');
+
+ ns.navTo('bar');
+ expect($window.location.href).toBe('http://server/#/bar');
+
+ ns.navTo('baz', { q1: 'thing1', q2: 'thing2' });
+ expect($window.location.href).toBe(
+ 'http://server/#/baz?q1=thing1&q2=thing2'
+ );
+
+ ns.navTo('zip', { q3: 'thing3' });
+ expect($window.location.href).toBe(
+ 'http://server/#/zip?q3=thing3'
+ );
+
+ ns.navTo('zoom', {});
+ expect($window.location.href).toBe('http://server/#/zoom');
+
+ ns.navTo('roof', [1, 2, 3]);
+ expect($window.location.href).toBe('http://server/#/roof');
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/rest-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/rest-spec.js
new file mode 100644
index 00000000..55e22782
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/rest-spec.js
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 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 -- Remote -- REST Service - Unit Tests
+ */
+describe('factory: fw/remote/rest.js', function() {
+ var $log, $httpBackend, fs, rs, promise;
+
+ beforeEach(module('onosUtil', 'onosRemote'));
+
+ beforeEach(module(function($provide) {
+ $provide.factory('$location', function (){
+ return {
+ protocol: function () { return 'http'; },
+ host: function () { return 'foo'; },
+ port: function () { return '80'; }
+ };
+ })
+ }));
+
+ beforeEach(inject(function (_$log_, _$httpBackend_, FnService, RestService) {
+ $log = _$log_;
+ $httpBackend = _$httpBackend_;
+ fs = FnService;
+ rs = RestService;
+ }));
+
+ it('should define RestService', function () {
+ expect(rs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(rs, [
+ 'get'
+ ])).toBeTruthy();
+ });
+
+ var mockData = {
+ id: 1,
+ prop: 'abc'
+ };
+
+ it('should fetch remote data', function () {
+ var called = 0,
+ capture = null;
+ $httpBackend.whenGET(/.*/).respond(mockData);
+ spyOn($log, 'warn');
+
+ rs.get('bar', function (data) {
+ called++;
+ capture = data;
+ });
+
+ expect(called).toEqual(0);
+ expect(capture).toBeNull();
+ $httpBackend.flush();
+ expect(called).toEqual(1);
+ expect(capture).toEqual(mockData);
+ expect($log.warn).not.toHaveBeenCalled();
+ });
+
+ it('should fail to fetch remote data', function () {
+ var called = 0,
+ capture = null;
+ $httpBackend.whenGET(/.*/).respond(404, 'Not Found');
+ spyOn($log, 'warn');
+
+ rs.get('bar', function (data) {
+ called++;
+ capture = data;
+ });
+
+ expect(called).toEqual(0);
+ expect(capture).toBeNull();
+ $httpBackend.flush();
+ expect(called).toEqual(0);
+ expect(capture).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(
+ 'Failed to retrieve JSON data: http://foo:80/onos/ui/rs/bar',
+ 404, 'Not Found');
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js
new file mode 100644
index 00000000..eddbf8b2
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js
@@ -0,0 +1,89 @@
+/*
+ * Copyright 2015 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 -- Remote -- General Functions - Unit Tests
+ */
+describe('factory: fw/remote/urlfn.js', function () {
+ var $log, $loc, ufs, fs;
+
+ var protocol, host, port;
+
+ beforeEach(module('onosRemote'));
+
+ beforeEach(module(function($provide) {
+ $provide.factory('$location', function (){
+ return {
+ protocol: function () { return protocol; },
+ host: function () { return host; },
+ port: function () { return port; }
+ };
+ })
+ }));
+
+ beforeEach(inject(function (_$log_, $location, UrlFnService, FnService) {
+ $log = _$log_;
+ $loc = $location;
+ ufs = UrlFnService;
+ fs = FnService;
+ }));
+
+ function setLoc(prot, h, p) {
+ protocol = prot;
+ host = h;
+ port = p;
+ }
+
+ it('should define UrlFnService', function () {
+ expect(ufs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ufs, [
+ 'rsUrl', 'wsUrl'
+ ])).toBeTruthy();
+ });
+
+ it('should return the correct (http) RS url', function () {
+ setLoc('http', 'foo', '123');
+ expect(ufs.rsUrl('path')).toEqual('http://foo:123/onos/ui/rs/path');
+ });
+
+ it('should return the correct (https) RS url', function () {
+ setLoc('https', 'foo', '123');
+ expect(ufs.rsUrl('path')).toEqual('https://foo:123/onos/ui/rs/path');
+ });
+
+ it('should return the correct (ws) WS url', function () {
+ setLoc('http', 'foo', '123');
+ expect(ufs.wsUrl('path')).toEqual('ws://foo:123/onos/ui/websock/path');
+ });
+
+ it('should return the correct (wss) WS url', function () {
+ setLoc('https', 'foo', '123');
+ expect(ufs.wsUrl('path')).toEqual('wss://foo:123/onos/ui/websock/path');
+ });
+
+ it('should allow us to define an alternate WS port', function () {
+ setLoc('http', 'foo', '123');
+ expect(ufs.wsUrl('xyyzy', 456)).toEqual('ws://foo:456/onos/ui/websock/xyyzy');
+ });
+
+ it('should allow us to define an alternate host', function () {
+ setLoc('http', 'foo', '123');
+ expect(ufs.wsUrl('core', 456, 'bar')).toEqual('ws://bar:456/onos/ui/websock/core');
+ });
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/websocket-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/websocket-spec.js
new file mode 100644
index 00000000..9673f16e
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/websocket-spec.js
@@ -0,0 +1,268 @@
+/*
+ * Copyright 2015 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 -- Remote -- Web Socket Service - Unit Tests
+ */
+describe('factory: fw/remote/websocket.js', function () {
+ var $log, fs, wss;
+
+ var noop = function () {},
+ send = jasmine.createSpy('send').and.callFake(function (ev) {
+ return ev;
+ }),
+ mockWebSocket = {
+ send: send
+ };
+
+ beforeEach(module('onosRemote', 'onosLayer', 'ngRoute', 'onosNav', 'onosSvg'));
+
+ beforeEach(function () {
+ mockWebSocket = {
+ send: send
+ };
+ });
+
+ beforeEach(function () {
+ module(function ($provide) {
+ $provide.factory('WSock', function () {
+ return {
+ newWebSocket: function () {
+ return mockWebSocket;
+ }
+ };
+ });
+ });
+ });
+
+ beforeEach(module(function($provide) {
+ $provide.factory('$location', function () {
+ return {
+ protocol: function () { return 'http'; },
+ host: function () { return 'foo'; },
+ port: function () { return '80'; }
+ };
+ })
+ }));
+
+ beforeEach(inject(function (_$log_, FnService, WebSocketService) {
+ $log = _$log_;
+ fs = FnService;
+ wss = WebSocketService;
+ wss.resetState();
+ }));
+
+
+ it('should define WebSocketService', function () {
+ expect(wss).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(wss, [
+ 'resetSid', 'resetState',
+ 'createWebSocket', 'bindHandlers', 'unbindHandlers',
+ 'addOpenListener', 'removeOpenListener', 'sendEvent'
+ ])).toBeTruthy();
+ });
+
+ it('should use the appropriate URL, createWebsocket', function () {
+ var url = wss.createWebSocket();
+ expect(url).toEqual('ws://foo:80/onos/ui/websock/core');
+ });
+
+ it('should use the appropriate URL with modified port, createWebsocket',
+ function () {
+ var url = wss.createWebSocket({ wsport: 1243 });
+ expect(url).toEqual('ws://foo:1243/onos/ui/websock/core');
+ });
+
+ it('should verify websocket event handlers, createWebsocket', function () {
+ wss.createWebSocket({ wsport: 1234 });
+ expect(fs.isF(mockWebSocket.onopen)).toBeTruthy();
+ expect(fs.isF(mockWebSocket.onmessage)).toBeTruthy();
+ expect(fs.isF(mockWebSocket.onclose)).toBeTruthy();
+ });
+
+ it('should invoke listener callbacks when websocket is up, handleOpen',
+ function () {
+ var num = 0;
+ function incrementNum() { num++; }
+ wss.addOpenListener(incrementNum);
+ wss.createWebSocket({ wsport: 1234 });
+ mockWebSocket.onopen();
+ expect(num).toBe(1);
+ });
+
+ it('should send pending events, handleOpen', function () {
+ var fakeEvent = {
+ event: 'mockEv',
+ sid: 1,
+ payload: { mock: 'thing' }
+ };
+ wss.sendEvent(fakeEvent.event, fakeEvent.payload);
+ expect(mockWebSocket.send).not.toHaveBeenCalled();
+ wss.createWebSocket({ wsport: 1234 });
+ mockWebSocket.onopen();
+ expect(mockWebSocket.send).toHaveBeenCalledWith(JSON.stringify(fakeEvent));
+ });
+
+ it('should handle an incoming bad JSON message, handleMessage', function () {
+ spyOn($log, 'error');
+ var badMsg = {
+ data: 'bad message'
+ };
+ wss.createWebSocket({ wsport: 1234 });
+ expect(mockWebSocket.onmessage(badMsg)).toBeNull();
+ expect($log.error).toHaveBeenCalled();
+ });
+
+ it('should verify message was handled, handleMessage', function () {
+ var num = 0,
+ fakeHandler = {
+ mockEvResp: function () { num++; }
+ },
+ data = JSON.stringify({
+ event: 'mockEvResp',
+ payload: {}
+ }),
+ event = {
+ data: data
+ };
+ wss.createWebSocket({ wsport: 1234 });
+ wss.bindHandlers(fakeHandler);
+ expect(mockWebSocket.onmessage(event)).toBe(undefined);
+ expect(num).toBe(1);
+ });
+
+ it('should warn if there is an unhandled event, handleMessage', function () {
+ spyOn($log, 'warn');
+ var data = { foo: 'bar', bar: 'baz'},
+ dataString = JSON.stringify(data),
+ badEv = {
+ data: dataString
+ };
+ wss.createWebSocket({ wsport: 1234 });
+ mockWebSocket.onmessage(badEv);
+ expect($log.warn).toHaveBeenCalledWith('Unhandled event:', data);
+ });
+
+ it('should not warn if valid input, bindHandlers', function () {
+ spyOn($log, 'warn');
+ expect(wss.bindHandlers({
+ foo: noop,
+ bar: noop
+ })).toBe(undefined);
+ expect($log.warn).not.toHaveBeenCalled();
+ });
+
+ it('should warn if no arguments, bindHandlers', function () {
+ spyOn($log, 'warn');
+ expect(wss.bindHandlers()).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(
+ 'WSS.bindHandlers(): no event handlers'
+ );
+ expect(wss.bindHandlers({})).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(
+ 'WSS.bindHandlers(): no event handlers'
+ );
+ });
+
+ it('should warn if handler is not a function, bindHandlers', function () {
+ spyOn($log, 'warn');
+ expect(wss.bindHandlers({
+ foo: 'handler1',
+ bar: 3,
+ baz: noop
+ })).toBe(undefined);
+ expect($log.warn).toHaveBeenCalledWith('foo handler not a function');
+ expect($log.warn).toHaveBeenCalledWith('bar handler not a function');
+ });
+
+ it('should warn if duplicate handlers were given, bindHandlers',
+ function () {
+ spyOn($log, 'warn');
+ wss.bindHandlers({
+ foo: noop
+ });
+ expect(wss.bindHandlers({
+ foo: noop
+ })).toBe(undefined);
+ expect($log.warn).toHaveBeenCalledWith('duplicate bindings ignored:',
+ ['foo']);
+ });
+
+ it('should warn if no arguments, unbindHandlers', function () {
+ spyOn($log, 'warn');
+ expect(wss.unbindHandlers()).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(
+ 'WSS.unbindHandlers(): no event handlers'
+ );
+ expect(wss.unbindHandlers({})).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(
+ 'WSS.unbindHandlers(): no event handlers'
+ );
+ });
+ // Note: cannot test unbindHandlers' forEach due to it using closure variable
+
+ it('should not warn if valid argument, addOpenListener', function () {
+ spyOn($log, 'warn');
+ var o = wss.addOpenListener(noop);
+ expect(o.id === 1);
+ expect(o.cb === noop);
+ expect($log.warn).not.toHaveBeenCalled();
+ o = wss.addOpenListener(noop);
+ expect(o.id === 2);
+ expect(o.cb === noop);
+ expect($log.warn).not.toHaveBeenCalled();
+ });
+
+ it('should log error if callback not a function, addOpenListener',
+ function () {
+ spyOn($log, 'error');
+ var o = wss.addOpenListener('foo');
+ expect(o.id === 1);
+ expect(o.cb === 'foo');
+ expect(o.error === 'No callback defined');
+ expect($log.error).toHaveBeenCalledWith(
+ 'WSS.addOpenListener(): callback not a function'
+ );
+ });
+
+ it('should not warn if valid listener object, removeOpenListener', function () {
+ spyOn($log, 'warn');
+ expect(wss.removeOpenListener({
+ id: 1,
+ cb: noop
+ })).toBe(undefined);
+ expect($log.warn).not.toHaveBeenCalled();
+ });
+
+ it('should warn if listener is invalid, removeOpenListener', function () {
+ spyOn($log, 'warn');
+ expect(wss.removeOpenListener({})).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(
+ 'WSS.removeOpenListener(): invalid listener', {}
+ );
+ expect(wss.removeOpenListener('listener')).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(
+ 'WSS.removeOpenListener(): invalid listener', 'listener'
+ );
+ });
+
+ // Note: handleClose is not currently tested due to all work it does relies
+ // on closure variables that cannot be mocked
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js
new file mode 100644
index 00000000..23d3153f
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2015 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 -- Remote -- Web Socket Event Service - Unit Tests
+ */
+describe('factory: fw/remote/wsevent.js', function () {
+ var $log, fs, wse;
+
+ beforeEach(module('onosRemote'));
+
+ beforeEach(inject(function (_$log_, FnService, WsEventService) {
+ $log = _$log_;
+ fs = FnService;
+ wse = WsEventService;
+ wse.resetSid();
+ }));
+
+
+ it('should define WsEventService', function () {
+ expect(wse).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(wse, [
+ 'sendEvent', 'resetSid'
+ ])).toBeTruthy();
+ });
+
+ var event,
+ fakeWs = {
+ send: function (ev) {
+ event = ev;
+ }
+ };
+
+ it('should construct an event object with no payload', function () {
+ wse.sendEvent(fakeWs, 'foo');
+ expect(event.event).toEqual('foo');
+ expect(event.sid).toEqual(1);
+ expect(event.payload).toEqual({});
+ });
+
+ it('should construct an event object with some payload', function () {
+ wse.sendEvent(fakeWs, 'bar', { val: 42 });
+ expect(event.event).toEqual('bar');
+ expect(event.sid).toEqual(1);
+ expect(event.payload).toEqual({ val: 42 });
+ });
+
+ it('should increment the sid', function () {
+ wse.sendEvent(fakeWs, 'one');
+ expect(event.event).toEqual('one');
+ expect(event.sid).toEqual(1);
+
+ wse.sendEvent(fakeWs, 'dos');
+ expect(event.event).toEqual('dos');
+ expect(event.sid).toEqual(2);
+
+ wse.sendEvent(fakeWs, 'tres');
+ expect(event.event).toEqual('tres');
+ expect(event.sid).toEqual(3);
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/geodata-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/geodata-spec.js
new file mode 100644
index 00000000..ccf4782f
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/geodata-spec.js
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2015 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 -- SVG -- GeoData Service - Unit Tests
+ */
+describe('factory: fw/svg/geodata.js', function() {
+ var $log, $httpBackend, fs, gds, promise;
+
+ beforeEach(module('onosUtil', 'onosSvg'));
+
+ beforeEach(inject(function (_$log_, _$httpBackend_, FnService, GeoDataService) {
+ $log = _$log_;
+ $httpBackend = _$httpBackend_;
+ fs = FnService;
+ gds = GeoDataService;
+ gds.clearCache();
+ }));
+
+
+ it('should define GeoDataService', function () {
+ expect(gds).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(gds, [
+ 'clearCache', 'fetchTopoData', 'createPathGenerator'
+ ])).toBeTruthy();
+ });
+
+ it('should return null when no parameters given', function () {
+ promise = gds.fetchTopoData();
+ expect(promise).toBeNull();
+ });
+
+ it('should augment the id of a bundled map', function () {
+ var id = '*foo';
+ promise = gds.fetchTopoData(id);
+ expect(promise.meta).toBeDefined();
+ expect(promise.meta.id).toBe(id);
+ expect(promise.meta.url).toBe('data/map/foo.json');
+ });
+
+ it('should treat an external id as the url itself', function () {
+ var id = 'some/path/to/foo';
+ promise = gds.fetchTopoData(id);
+ expect(promise.meta).toBeDefined();
+ expect(promise.meta.id).toBe(id);
+ expect(promise.meta.url).toBe(id + '.json');
+ });
+
+ it('should cache the returned objects', function () {
+ var id = 'foo';
+ promise = gds.fetchTopoData(id);
+ expect(promise).toBeDefined();
+ expect(promise.meta.wasCached).toBeFalsy();
+ expect(promise.tagged).toBeUndefined();
+
+ promise.tagged = 'I woz here';
+
+ promise = gds.fetchTopoData(id);
+ expect(promise).toBeDefined();
+ expect(promise.meta.wasCached).toBeTruthy();
+ expect(promise.tagged).toEqual('I woz here');
+ });
+
+ it('should clear the cache when asked', function () {
+ var id = 'foo';
+ promise = gds.fetchTopoData(id);
+ expect(promise.meta.wasCached).toBeFalsy();
+
+ promise = gds.fetchTopoData(id);
+ expect(promise.meta.wasCached).toBeTruthy();
+
+ gds.clearCache();
+ promise = gds.fetchTopoData(id);
+ expect(promise.meta.wasCached).toBeFalsy();
+ });
+
+
+ it('should log a warning if data fails to load', function () {
+ var id = 'foo';
+ $httpBackend.expectGET('foo.json').respond(404, 'Not found');
+ spyOn($log, 'warn');
+
+ promise = gds.fetchTopoData(id);
+ $httpBackend.flush();
+ expect(promise.topodata).toBeUndefined();
+ expect($log.warn)
+ .toHaveBeenCalledWith('Failed to retrieve map TopoJSON data: foo.json',
+ 404, 'Not found');
+ });
+
+ // --- path generator tests
+
+ function simpleTopology(object) {
+ return {
+ type: "Topology",
+ transform: {scale: [1, 1], translate: [0, 0]},
+ objects: {states: object},
+ arcs: [
+ [[0, 0], [1, 0], [0, 1], [-1, 0], [0, -1]],
+ [[0, 0], [1, 0], [0, 1]],
+ [[1, 1], [-1, 0], [0, -1]],
+ [[1, 1]],
+ [[0, 0]]
+ ]
+ };
+ }
+
+ function simpleLineStringTopo() {
+ return simpleTopology({type: "LineString", arcs: [1, 2]});
+ }
+
+ it('should use default settings if none are supplied', function () {
+ var gen = gds.createPathGenerator(simpleLineStringTopo());
+ expect(gen.settings.objectTag).toBe('states');
+ expect(gen.settings.logicalSize).toBe(1000);
+ expect(gen.settings.mapFillScale).toBe(.95);
+ // best we can do for now is test that projection is a function ...
+ expect(fs.isF(gen.settings.projection)).toBeTruthy();
+ });
+
+ it('should allow us to override default settings', function () {
+ var gen = gds.createPathGenerator(simpleLineStringTopo(), {
+ mapFillScale: .80
+ });
+ expect(gen.settings.objectTag).toBe('states');
+ expect(gen.settings.logicalSize).toBe(1000);
+ expect(gen.settings.mapFillScale).toBe(.80);
+ });
+
+ it('should create transformed geodata, and a path generator', function () {
+ var gen = gds.createPathGenerator(simpleLineStringTopo());
+ expect(fs.isO(gen.settings)).toBeTruthy();
+ expect(fs.isO(gen.geodata)).toBeTruthy();
+ expect(fs.isF(gen.pathgen)).toBeTruthy();
+ });
+ // NOTE: we probably should have more unit tests that assert stuff about
+ // the transformed data (geo data) -- though perhaps we can rely on
+ // the unit testing of TopoJSON? See...
+ // https://github.com/mbostock/topojson/blob/master/test/feature-test.js
+ // and, what about the path generator?, and the computed bounds?
+ // In summary, more work should be done here..
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
new file mode 100644
index 00000000..9709ec73
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js
@@ -0,0 +1,425 @@
+/*
+ * Copyright 2015 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 -- SVG -- Glyph Service - Unit Tests
+ */
+describe('factory: fw/svg/glyph.js', function() {
+ var $log, fs, gs, d3Elem, svg;
+
+ var numBaseGlyphs = 42,
+ vbBird = '352 224 113 112',
+ vbGlyph = '0 0 110 110',
+ vbBadge = '0 0 10 10',
+ longPrefix = 'M95.8,9.2H14.2c-2.8,0-5,2.2-5,5v81.5c0,2.8,2.2,5,5,' +
+ '5h81.5c2.8,0,5-2.2,5-5V14.2C100.8,11.5,98.5,9.2,95.8,9.2z ',
+ tablePrefix = 'M15.9,19.1h-8v-13h8V19.1z M90.5,6.1H75.6v13h14.9V6.1' +
+ 'z M71.9,6.1H56.9v13h14.9V6.1z M53.2,6.1H38.3v13h14.9V6.1z M34.5,' +
+ '6.1H19.6v13h14.9V6.1z M102.2,6.1h-8v13h8V6.1z ',
+ prefixLookup = {
+ bird: 'M427.7,300.4',
+ unknown: 'M35,40a5',
+ node: 'M15,100a5',
+ switch: 'M10,20a10',
+ roadm: 'M10,35l25-',
+ endstation: 'M10,15a5,5',
+ router: 'M10,55A45,45',
+ bgpSpeaker: 'M10,40a45,35',
+ chain: 'M60.4,77.6c-',
+ crown: 'M99.5,21.6c0,',
+ lock: 'M79.4,48.6h',
+ topo: 'M97.2,76.3H86.6',
+ refresh: 'M102.6,40.8L88.4',
+ garbage: 'M94.6,20.2c',
+
+ // navigation specific glyphs
+ flowTable: tablePrefix + 'M102.2,23.6H7.9v',
+ portTable: tablePrefix + 'M102.6,23.6v78.5H',
+ groupTable: 'M16,19.1H8v-13h',
+
+ // toolbar specific glyphs
+ summary: longPrefix + 'M16.7',
+ details: longPrefix + 'M16.9',
+ ports: 'M98,9.2H79.6c',
+ map: 'M95.8,9.2H14.2c-2.8,0-5,2.2-5,5v66',
+ cycleLabels: 'M72.5,33.9c',
+ oblique: 'M80.9,30.2h',
+ filters: 'M24.8,13.3L',
+ resetZoom: 'M86,79.8L',
+ relatedIntents: 'M99.9,43.7',
+ nextIntent: 'M88.1,55.7',
+ prevIntent: 'M22.5,55.6',
+ intentTraffic: 'M14.7,71.5h',
+ allTraffic: 'M15.7,64.5h-7v',
+ flows: 'M93.8,46.1c',
+ eqMaster: 'M100.1,46.9l',
+
+ // badges
+ uiAttached: 'M2,2.5a.5,.5',
+ checkMark: 'M2.6,4.5c0',
+ xMark: 'M9.0,7.2C8.2',
+ triangleUp: 'M0.5,6.2c0',
+ triangleDown: 'M9.5,4.2c0',
+ plus: 'M4,2h2v2h2v2',
+ minus: 'M2,4h6v2',
+ play: 'M2.5,2l5.5,3',
+ stop: 'M2.5,2.5h5',
+
+ cloud: 'M37.6,79.5c-6.9,8.7-20.4,8.6',
+
+ // our test ones..
+ triangle: 'M.5,.2',
+ diamond: 'M.2,.5'
+ },
+ glyphIds = [
+ 'unknown', 'node', 'switch', 'roadm', 'endstation', 'router',
+ 'bgpSpeaker', 'chain', 'crown', 'lock', 'topo', 'refresh',
+ 'garbage',
+ 'flowTable', 'portTable', 'groupTable',
+ 'summary', 'details', 'ports', 'map', 'cycleLabels',
+ 'oblique', 'filters', 'resetZoom', 'relatedIntents', 'nextIntent',
+ 'prevIntent', 'intentTraffic', 'allTraffic', 'flows', 'eqMaster'
+ ],
+ badgeIds = [
+ 'uiAttached', 'checkMark', 'xMark', 'triangleUp', 'triangleDown',
+ 'plus', 'minus', 'play', 'stop'
+ ],
+ spriteIds = [
+ 'cloud'
+ ];
+
+ beforeEach(module('onosUtil', 'onosSvg'));
+
+ beforeEach(inject(function (_$log_, FnService, GlyphService) {
+ var body = d3.select('body');
+ $log = _$log_;
+ fs = FnService;
+ gs = GlyphService;
+ d3Elem = body.append('defs').attr('id', 'myDefs');
+ svg = body.append('svg').attr('id', 'mySvg');
+ }));
+
+ afterEach(function () {
+ d3.select('#mySvg').remove();
+ d3.select('#myDefs').remove();
+ gs.clear();
+ });
+
+ it('should define GlyphService', function () {
+ expect(gs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(gs, [
+ 'clear', 'init', 'registerGlyphs', 'registerGlyphSet',
+ 'ids', 'glyph', 'loadDefs', 'addGlyph'
+ ])).toBe(true);
+ });
+
+ it('should start with no glyphs loaded', function () {
+ expect(gs.ids()).toEqual([]);
+ });
+
+ it('should load the base set of glyphs into the cache', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ });
+
+ it('should remove glyphs from the cache on clear', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ gs.clear();
+ expect(gs.ids().length).toEqual(0);
+ });
+
+ function verifyGlyphLoadedInCache(id, vbox, expPfxId) {
+ var pfxId = expPfxId || id,
+ glyph = gs.glyph(id),
+ prefix = prefixLookup[pfxId],
+ plen = prefix.length;
+ expect(fs.contains(gs.ids(), id)).toBe(true);
+ expect(glyph).toBeDefined();
+ expect(glyph.id).toEqual(id);
+ expect(glyph.vb).toEqual(vbox);
+ expect(glyph.d.slice(0, plen)).toEqual(prefix);
+ }
+
+ it('should be configured with the correct number of glyphs', function () {
+ var nGlyphs = 1 + glyphIds.length + badgeIds.length + spriteIds.length;
+ expect(nGlyphs).toEqual(numBaseGlyphs);
+ });
+
+ it('should load the bird glyph', function() {
+ gs.init();
+ verifyGlyphLoadedInCache('bird', vbBird);
+ });
+
+ it('should load the regular glyphs', function () {
+ gs.init();
+ glyphIds.forEach(function (id) {
+ verifyGlyphLoadedInCache(id, vbGlyph);
+ });
+ });
+
+ it('should load the badge glyphs', function () {
+ gs.init();
+ badgeIds.forEach(function (id) {
+ verifyGlyphLoadedInCache(id, vbBadge);
+ });
+ });
+
+ it('should load the sprites', function () {
+ gs.init();
+ spriteIds.forEach(function (id) {
+ verifyGlyphLoadedInCache(id, vbGlyph);
+ });
+ });
+
+
+ // define some glyphs that we want to install
+
+ var testVbox = '0 0 1 1',
+ triVbox = '0 0 12 12',
+ diaVbox = '0 0 15 15',
+ dTriangle = 'M.5,.2l.3,.6,h-.6z',
+ dDiamond = 'M.2,.5l.3,-.3l.3,.3l-.3,.3z',
+ newGlyphs = {
+ _viewbox: testVbox,
+ triangle: dTriangle,
+ diamond: dDiamond
+ },
+ dupGlyphs = {
+ _viewbox: testVbox,
+ router: dTriangle,
+ switch: dDiamond
+ },
+ altNewGlyphs = {
+ _triangle: triVbox,
+ triangle: dTriangle,
+ _diamond: diaVbox,
+ diamond: dDiamond
+ },
+ altDupGlyphs = {
+ _router: triVbox,
+ router: dTriangle,
+ _switch: diaVbox,
+ switch: dDiamond
+ },
+ badGlyphSet = {
+ triangle: dTriangle,
+ diamond: dDiamond
+ },
+ warnMsg = 'GlyphService.registerGlyphs(): ',
+ warnMsgSet = 'GlyphService.registerGlyphSet(): ',
+ idCollision = warnMsg + 'ID collision: ',
+ idCollisionSet = warnMsgSet + 'ID collision: ',
+ missVbSet = warnMsgSet + 'no "_viewbox" property found',
+ missVbCustom = warnMsg + 'Missing viewbox property: ',
+ missVbTri = missVbCustom + '"_triangle"',
+ missVbDia = missVbCustom + '"_diamond"';
+
+
+ it('should install new glyphs as a glyph-set', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphSet(newGlyphs);
+ expect(ok).toBe(true);
+ expect($log.warn).not.toHaveBeenCalled();
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs + 2);
+ verifyGlyphLoadedInCache('triangle', testVbox);
+ verifyGlyphLoadedInCache('diamond', testVbox);
+ });
+
+ it('should not overwrite glyphs (via glyph-set) with dup IDs', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphSet(dupGlyphs);
+ expect(ok).toBe(false);
+ expect($log.warn).toHaveBeenCalledWith(idCollisionSet + '"switch"');
+ expect($log.warn).toHaveBeenCalledWith(idCollisionSet + '"router"');
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ // verify original glyphs still exist...
+ verifyGlyphLoadedInCache('router', vbGlyph);
+ verifyGlyphLoadedInCache('switch', vbGlyph);
+ });
+
+ it('should replace glyphs (via glyph-set) if asked nicely', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphSet(dupGlyphs, true);
+ expect(ok).toBe(true);
+ expect($log.warn).not.toHaveBeenCalled();
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ // verify glyphs have been overwritten...
+ verifyGlyphLoadedInCache('router', testVbox, 'triangle');
+ verifyGlyphLoadedInCache('switch', testVbox, 'diamond');
+ });
+
+ it ('should complain if missing _viewbox in a glyph-set', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphSet(badGlyphSet);
+ expect(ok).toBe(false);
+ expect($log.warn).toHaveBeenCalledWith(missVbSet);
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ });
+
+ it('should install new glyphs', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphs(altNewGlyphs);
+ expect(ok).toBe(true);
+ expect($log.warn).not.toHaveBeenCalled();
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs + 2);
+ verifyGlyphLoadedInCache('triangle', triVbox);
+ verifyGlyphLoadedInCache('diamond', diaVbox);
+ });
+
+ it('should not overwrite glyphs with dup IDs', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphs(altDupGlyphs);
+ expect(ok).toBe(false);
+ expect($log.warn).toHaveBeenCalledWith(idCollision + '"switch"');
+ expect($log.warn).toHaveBeenCalledWith(idCollision + '"router"');
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ // verify original glyphs still exist...
+ verifyGlyphLoadedInCache('router', vbGlyph);
+ verifyGlyphLoadedInCache('switch', vbGlyph);
+ });
+
+ it('should replace glyphs if asked nicely', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphs(altDupGlyphs, true);
+ expect(ok).toBe(true);
+ expect($log.warn).not.toHaveBeenCalled();
+
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ // verify glyphs have been overwritten...
+ verifyGlyphLoadedInCache('router', triVbox, 'triangle');
+ verifyGlyphLoadedInCache('switch', diaVbox, 'diamond');
+ });
+
+ it ('should complain if missing custom viewbox', function () {
+ gs.init();
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ spyOn($log, 'warn');
+
+ var ok = gs.registerGlyphs(badGlyphSet);
+ expect(ok).toBe(false);
+ expect($log.warn).toHaveBeenCalledWith(missVbTri);
+ expect($log.warn).toHaveBeenCalledWith(missVbDia);
+ expect(gs.ids().length).toEqual(numBaseGlyphs);
+ });
+
+ function verifyPathPrefix(elem, prefix) {
+ var plen = prefix.length,
+ d = elem.select('path').attr('d');
+ expect(d.slice(0, plen)).toEqual(prefix);
+ }
+
+ function verifyLoadedInDom(id, vb, expPfxId) {
+ var pfxId = expPfxId || id,
+ symbol = d3Elem.select('#' + id);
+ expect(symbol.size()).toEqual(1);
+ expect(symbol.attr('viewBox')).toEqual(vb);
+ verifyPathPrefix(symbol, prefixLookup[pfxId]);
+ }
+
+ it('should load base glyphs into the DOM', function () {
+ gs.init();
+ gs.loadDefs(d3Elem);
+ expect(d3Elem.selectAll('symbol').size()).toEqual(numBaseGlyphs);
+ verifyLoadedInDom('bgpSpeaker', vbGlyph);
+ });
+
+ it('should load custom glyphs into the DOM', function () {
+ gs.init();
+ gs.registerGlyphSet(newGlyphs);
+ gs.loadDefs(d3Elem);
+ expect(d3Elem.selectAll('symbol').size()).toEqual(numBaseGlyphs + 2);
+ verifyLoadedInDom('diamond', testVbox);
+ });
+
+ it('should load only specified glyphs into the DOM', function () {
+ gs.init();
+ gs.loadDefs(d3Elem, ['crown', 'chain', 'node']);
+ expect(d3Elem.selectAll('symbol').size()).toEqual(3);
+ verifyLoadedInDom('crown', vbGlyph);
+ verifyLoadedInDom('chain', vbGlyph);
+ verifyLoadedInDom('node', vbGlyph);
+ });
+
+ it('should add a glyph with default size', function () {
+ gs.init();
+ var retval = gs.addGlyph(svg, 'crown');
+ var what = svg.selectAll('use');
+ expect(what.size()).toEqual(1);
+ expect(what.attr('width')).toEqual('40');
+ expect(what.attr('height')).toEqual('40');
+ expect(what.attr('xlink:href')).toEqual('#crown');
+ expect(what.classed('glyph')).toBeTruthy();
+ expect(what.classed('overlay')).toBeFalsy();
+
+ // check a couple on retval, which should be the same thing..
+ expect(retval.attr('xlink:href')).toEqual('#crown');
+ expect(retval.classed('glyph')).toBeTruthy();
+ });
+
+ it('should add a glyph with given size', function () {
+ gs.init();
+ gs.addGlyph(svg, 'crown', 37);
+ var what = svg.selectAll('use');
+ expect(what.size()).toEqual(1);
+ expect(what.attr('width')).toEqual('37');
+ expect(what.attr('height')).toEqual('37');
+ expect(what.attr('xlink:href')).toEqual('#crown');
+ expect(what.classed('glyph')).toBeTruthy();
+ expect(what.classed('overlay')).toBeFalsy();
+ });
+
+ it('should add a glyph marked as overlay', function () {
+ gs.init();
+ gs.addGlyph(svg, 'crown', 20, true);
+ var what = svg.selectAll('use');
+ expect(what.size()).toEqual(1);
+ expect(what.attr('width')).toEqual('20');
+ expect(what.attr('height')).toEqual('20');
+ expect(what.attr('xlink:href')).toEqual('#crown');
+ expect(what.classed('glyph')).toBeTruthy();
+ expect(what.classed('overlay')).toBeTruthy();
+ });
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/icon-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/icon-spec.js
new file mode 100644
index 00000000..99d7bc38
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/icon-spec.js
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2015 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 -- SVG -- Icon Service - Unit Tests
+ */
+describe('factory: fw/svg/icon.js', function() {
+ var is, d3Elem;
+
+ var viewBox = '0 0 50 50',
+ glyphSize = '50',
+ iconSize = '20';
+
+
+ beforeEach(module('onosSvg'));
+
+ beforeEach(inject(function (IconService) {
+ is = IconService;
+ d3Elem = d3.select('body').append('div').attr('id', 'myDiv');
+ }));
+
+ afterEach(function () {
+ d3.select('#myDiv').remove();
+ });
+
+ it('should define IconService', function () {
+ expect(is).toBeDefined();
+ });
+
+ function checkElemSize(elem, dim) {
+ expect(elem.attr('width')).toEqual(dim);
+ expect(elem.attr('height')).toEqual(dim);
+ }
+
+ function verifyIconStructure(iconClass, useHref, iSize, vBox, gSize) {
+ var isz = iSize || iconSize,
+ vbx = vBox || viewBox,
+ gsz = gSize || glyphSize;
+
+ var svg = d3Elem.selectAll('svg');
+ expect(svg.size()).toBe(1);
+ checkElemSize(svg, isz);
+ expect(svg.attr('viewBox')).toEqual(vbx);
+
+ var g = svg.selectAll('g');
+ expect(g.size()).toBe(1);
+ expect(g.classed('icon')).toBeTruthy();
+ expect(g.classed(iconClass)).toBeTruthy();
+
+ var rect = g.select('rect');
+ expect(rect.size()).toBe(1);
+ checkElemSize(rect, gsz);
+ expect(rect.attr('rx')).toEqual('5');
+
+ if (useHref) {
+ var use = g.select('use');
+ expect(use.classed('glyph')).toBeTruthy();
+ expect(use.attr('xlink:href')).toEqual(useHref);
+ checkElemSize(use, gsz);
+ }
+ }
+
+ it('should load an icon into a div', function () {
+ expect(d3Elem.html()).toEqual('');
+ is.loadIconByClass(d3Elem, 'active');
+ verifyIconStructure('active', '#checkMark');
+ });
+
+ it('should allow us to specify the icon size', function () {
+ expect(d3Elem.html()).toEqual('');
+ is.loadIconByClass(d3Elem, 'inactive', 32);
+ verifyIconStructure('inactive', '#xMark', '32');
+ });
+
+ it('should verify triangleUp icon', function () {
+ expect(d3Elem.html()).toEqual('');
+ is.loadIconByClass(d3Elem, 'upArrow', 10);
+ verifyIconStructure('upArrow', '#triangleUp', '10');
+ });
+
+ it('should verify triangleDown icon', function () {
+ expect(d3Elem.html()).toEqual('');
+ is.loadIconByClass(d3Elem, 'downArrow', 10);
+ verifyIconStructure('downArrow', '#triangleDown', '10');
+ });
+
+ it('should verify no icon is displayed', function () {
+ expect(d3Elem.html()).toEqual('');
+ is.loadIconByClass(d3Elem, 'tableColSortNone', 10);
+ verifyIconStructure('tableColSortNone', null, '10');
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js
new file mode 100644
index 00000000..27848d1a
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2015 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 -- SVG -- Map Service - Unit Tests
+ */
+describe('factory: fw/svg/map.js', function() {
+ var $log, $httpBackend, fs, ms, d3Elem, promise;
+
+ beforeEach(module('onosUtil', 'onosSvg'));
+
+ beforeEach(inject(function (_$log_, _$httpBackend_, FnService, MapService) {
+ $log = _$log_;
+ $httpBackend = _$httpBackend_;
+ fs = FnService;
+ ms = MapService;
+ //ms.clearCache();
+ d3Elem = d3.select('body').append('svg').append('g').attr('id', 'mapLayer');
+ }));
+
+ afterEach(function () {
+ d3.select('svg').remove();
+ });
+
+ it('should define MapService', function () {
+ expect(ms).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ms, [
+ 'loadMapInto'
+ ])).toBeTruthy();
+ });
+
+ var fakeMapId = '../tests/app/fw/svg/fake-map-data',
+ fakeMapUrl = fakeMapId + '.json';
+
+ var fakeMapData = {
+ "type": "Topology",
+ "objects": {
+ "states": {
+ "type": "GeometryCollection",
+ "geometries": [
+ { "type": "Polygon", "arcs": [[0, 1]]},
+ { "type": "Polygon", "arcs": [[2, 3]]}
+ ]
+ }
+ },
+ "arcs": [
+ [ [6347, 2300], [ -16, -9], [ -22, 1], [ -5, 3], [ 9, 6], [ 27, 7], [ 7, -8]],
+ [ [6447, 2350], [ -4, -4], [ -19, -41], [ -66, -14], [ 4, 9], [ 14, 2]],
+ [ [6290, 2347], [ -2, 83], [ -2, 76], [ -2, 75], [ -2, 76], [ -2, 76], [ -2, 75]],
+ [ [6329, 4211], [ -3, 6], [ -2, 4], [ 2, 1], [ 28, -1], [ 28, 0]]
+ ],
+ "transform": {
+ "scale": [0.005772872856602365, 0.0024829805705001468],
+ "translate": [-124.70997774915153, 24.542349340056283]
+ }
+ };
+
+
+ it('should load map into layer', function () {
+ $httpBackend.expectGET(fakeMapUrl).respond(fakeMapData);
+
+ var obj = ms.loadMapInto(d3Elem, fakeMapId);
+ //$httpBackend.flush();
+ // TODO: figure out how to test this function as a black box test.
+
+ expect(obj).toBeTruthy();
+
+ // todo: assert that paths are added to map layer element
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js
new file mode 100644
index 00000000..ab2bfa3e
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js
@@ -0,0 +1,237 @@
+/*
+ * Copyright 2015 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 -- SVG -- SVG Util Service - Unit Tests
+ */
+describe('factory: fw/svg/svgUtil.js', function() {
+ var $log, fs, sus, svg, defs, force;
+
+ var noop = function () {};
+
+ beforeEach(module('onosUtil', 'onosSvg'));
+
+ beforeEach(inject(function (_$log_, FnService, SvgUtilService) {
+ $log = _$log_;
+ fs = FnService;
+ sus = SvgUtilService;
+ svg = d3.select('body').append('svg').attr('id', 'mySvg');
+ defs = svg.append('defs');
+ force = d3.layout.force();
+ }));
+
+ afterEach(function () {
+ d3.select('svg').remove();
+ });
+
+ it('should define SvgUtilService', function () {
+ expect(sus).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(sus, [
+ 'createDragBehavior', 'loadGlowDefs', 'cat7',
+ 'translate', 'scale', 'skewX', 'rotate',
+ 'stripPx', 'safeId', 'visible'
+ ])).toBeTruthy();
+ });
+
+ // === createDragBehavior
+ // TODO: break up drag into separate functions for testing
+ // d3 needs better testing support...
+
+ // Note: just checking to see if error message was called
+ // because jasmine spy isn't catching the right newline char
+ it('should complain if function given no parameters', function () {
+ spyOn($log, 'error');
+ expect(sus.createDragBehavior()).toBeNull();
+ expect($log.error).toHaveBeenCalled();
+ });
+
+ it('should complain if function is not given clickEnabled', function () {
+ spyOn($log, 'error');
+ expect(sus.createDragBehavior(force, noop, noop, noop)).toBeNull();
+ expect($log.error).toHaveBeenCalled();
+ });
+
+ it('should complain if function is not given dragEnabled', function () {
+ spyOn($log, 'error');
+ expect(sus.createDragBehavior(force, noop, noop)).toBeNull();
+ expect($log.error).toHaveBeenCalled();
+ });
+
+ it('should complain if function is not given atDragEnd', function () {
+ spyOn($log, 'error');
+ expect(sus.createDragBehavior(force, noop)).toBeNull();
+ expect($log.error).toHaveBeenCalled();
+ });
+
+ it('should complain if function is not given selectCb', function () {
+ spyOn($log, 'error');
+ expect(sus.createDragBehavior(force)).toBeNull();
+ expect($log.error).toHaveBeenCalled();
+ });
+
+ // === loadGlowDefs
+ function checkAttrs(glow, r, g, b) {
+ var filterEffects, feColor, feBlur, feMerge, feMergeNodes;
+
+ // filter attrs
+ expect(glow.attr('x')).toBe('-50%');
+ expect(glow.attr('y')).toBe('-50%');
+ expect(glow.attr('width')).toBe('200%');
+ expect(glow.attr('height')).toBe('200%');
+
+ filterEffects = d3.selectAll(glow.node().childNodes);
+ expect(filterEffects.size()).toBe(3);
+
+ // Note: d3 didn't recognize 'feColorMatrix' and others as valid selectors
+ // this is a work around
+ feColor = d3.select(filterEffects[0].shift());
+ feBlur = d3.select(filterEffects[0].shift());
+ feMerge = d3.select(filterEffects[0].shift());
+
+ // feColorMatrix attrs
+ expect(feColor.empty()).toBe(false);
+ expect(feColor.attr('type')).toBe('matrix');
+ expect(feColor.attr('values')).toBe(
+ '0 0 0 0 ' + r + ' ' +
+ '0 0 0 0 ' + g + ' ' +
+ '0 0 0 0 ' + b + ' ' +
+ '0 0 0 1 0 '
+ );
+
+ // feGuassianBlur attrs
+ expect(feBlur.empty()).toBe(false);
+ expect(feBlur.attr('stdDeviation')).toBe('3');
+ expect(feBlur.attr('result')).toBe('coloredBlur');
+
+ // feMerge attrs
+ feMergeNodes = d3.selectAll(feMerge.node().childNodes);
+ expect(feMergeNodes.size()).toBe(2);
+ expect(d3.select(feMergeNodes[0][0]).attr('in')).toBe('coloredBlur');
+ expect(d3.select(feMergeNodes[0][1]).attr('in')).toBe('SourceGraphic');
+ }
+
+ it('should load glow definitions', function () {
+ var blue, yellow;
+ sus.loadGlowDefs(defs);
+
+ expect(defs.empty()).toBe(false);
+ expect((defs.selectAll('filter')).size()).toBe(2);
+
+ // blue-glow specific
+ blue = defs.select('#blue-glow');
+ expect(blue.empty()).toBe(false);
+ checkAttrs(blue, 0.0, 0.0, 0.7);
+
+ // yellow-glow specific
+ yellow = defs.select('#yellow-glow');
+ expect(yellow.empty()).toBe(false);
+ checkAttrs(yellow, 1.0, 1.0, 0.3);
+ });
+
+ // === cat7
+
+ it('should define two methods on the api', function () {
+ var cat7 = sus.cat7();
+ expect(fs.areFunctions(cat7, [
+ 'testCard', 'getColor'
+ ])).toBeTruthy();
+ });
+
+ it('should provide a certain shade of blue', function () {
+ expect(sus.cat7().getColor('foo', false, 'light')).toEqual('#3E5780');
+ });
+
+ it('should not matter what the ID really is for shade of blue', function () {
+ expect(sus.cat7().getColor('bar', false, 'light')).toEqual('#3E5780');
+ });
+
+ it('should provide different shade of blue for muted', function () {
+ expect(sus.cat7().getColor('foo', true, 'light')).toEqual('#A8B8CC');
+ });
+
+
+ it('should provide an alternate (dark) shade of blue', function () {
+ expect(sus.cat7().getColor('foo', false, 'dark')).toEqual('#304860');
+ });
+
+ it('should provide an alternate (dark) shade of blue for muted', function () {
+ expect(sus.cat7().getColor('foo', true, 'dark')).toEqual('#304860');
+ });
+
+ it('should iterate across the colors', function () {
+ expect(sus.cat7().getColor('foo', false, 'light')).toEqual('#3E5780');
+ expect(sus.cat7().getColor('bar', false, 'light')).toEqual('#78533B');
+ expect(sus.cat7().getColor('baz', false, 'light')).toEqual('#CB4D28');
+ expect(sus.cat7().getColor('goo', false, 'light')).toEqual('#018D61');
+ expect(sus.cat7().getColor('zoo', false, 'light')).toEqual('#8A2979');
+ expect(sus.cat7().getColor('pip', false, 'light')).toEqual('#006D73');
+ expect(sus.cat7().getColor('sdh', false, 'light')).toEqual('#56AF00');
+ // and cycle back to the first color for item #8
+ expect(sus.cat7().getColor('bri', false, 'light')).toEqual('#3E5780');
+ // and return the same color for the same ID
+ expect(sus.cat7().getColor('zoo', false, 'light')).toEqual('#8A2979');
+ });
+
+ // === translate(), scale(), skewX(), rotate()
+
+ it('should translate from two args', function () {
+ expect(sus.translate(1,2)).toEqual('translate(1,2)');
+ });
+
+ it('should translate from an array', function () {
+ expect(sus.translate([3,4])).toEqual('translate(3,4)');
+ });
+
+ it('should scale', function () {
+ expect(sus.scale(1.5,2.5)).toEqual('scale(1.5,2.5)');
+ });
+
+ it('should skewX', function () {
+ expect(sus.skewX(3.14)).toEqual('skewX(3.14)');
+ });
+
+ it('should rotate', function () {
+ expect(sus.rotate(45)).toEqual('rotate(45)');
+ });
+
+
+ // === stripPx()
+
+ it('should not affect a number', function () {
+ expect(sus.stripPx('4')).toEqual('4');
+ });
+
+ it('should remove trailing px', function () {
+ expect(sus.stripPx('4px')).toEqual('4');
+ });
+
+ // === visible()
+
+ it('should hide and show an element', function () {
+ var r = svg.append('rect');
+
+ sus.visible(r, false);
+ expect(r.style('visibility')).toEqual('hidden');
+ expect(sus.visible(r)).toBe(false);
+
+ sus.visible(r, true);
+ expect(r.style('visibility')).toEqual('visible');
+ expect(sus.visible(r)).toBe(true);
+ });
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js
new file mode 100644
index 00000000..d70c87fb
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2015 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 -- SVG -- Zoom Service - Unit Tests
+ */
+describe('factory: fw/svg/zoom.js', function() {
+ var $log, fs, zs, svg, zoomLayer, zoomer;
+
+ var cz = 'ZoomService.createZoomer(): ',
+ d3s = ' (D3 selection) property defined';
+
+ beforeEach(module('onosUtil', 'onosSvg'));
+
+ beforeEach(inject(function (_$log_, FnService, ZoomService) {
+ $log = _$log_;
+ fs = FnService;
+ zs = ZoomService;
+ svg = d3.select('body').append('svg').attr('id', 'mySvg');
+ zoomLayer = svg.append('g').attr('id', 'myZoomlayer');
+ }));
+
+ afterEach(function () {
+ d3.select('#mySvg').remove();
+ // Note: since zoomLayer is a child of svg, it should be removed also
+ });
+
+ it('should define ZoomService', function () {
+ expect(zs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(zs, ['createZoomer'])).toBeTruthy();
+ });
+
+ function verifyZoomerApi() {
+ expect(fs.areFunctions(zoomer, [
+ 'panZoom', 'reset', 'translate', 'scale', 'scaleExtent'
+ ])).toBeTruthy();
+ }
+
+ it('should fail gracefully with no option object', function () {
+ spyOn($log, 'error');
+
+ zoomer = zs.createZoomer();
+ expect($log.error).toHaveBeenCalledWith(cz + 'No "svg" (svg tag)' + d3s);
+ expect($log.error).toHaveBeenCalledWith(cz + 'No "zoomLayer" (g tag)' + d3s);
+ expect(zoomer).toBeNull();
+ });
+
+ it('should complain if we miss required options', function () {
+ spyOn($log, 'error');
+
+ zoomer = zs.createZoomer({});
+ expect($log.error).toHaveBeenCalledWith(cz + 'No "svg" (svg tag)' + d3s);
+ expect($log.error).toHaveBeenCalledWith(cz + 'No "zoomLayer" (g tag)' + d3s);
+ expect(zoomer).toBeNull();
+ });
+
+ it('should work with minimal parameters', function () {
+ spyOn($log, 'error');
+
+ zoomer = zs.createZoomer({
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ expect($log.error).not.toHaveBeenCalled();
+ verifyZoomerApi();
+ });
+
+ it('should start at scale 1 and translate 0,0', function () {
+ zoomer = zs.createZoomer({
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ verifyZoomerApi();
+ expect(zoomer.translate()).toEqual([0,0]);
+ expect(zoomer.scale()).toEqual(1);
+ });
+
+ it('should allow programmatic pan/zoom', function () {
+ zoomer = zs.createZoomer({
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ verifyZoomerApi();
+ expect(zoomer.translate()).toEqual([0,0]);
+ expect(zoomer.scale()).toEqual(1);
+
+ zoomer.panZoom([20,30], 3);
+ expect(zoomer.translate()).toEqual([20,30]);
+ expect(zoomer.scale()).toEqual(3);
+ });
+
+ it('should provide default scale extent', function () {
+ zoomer = zs.createZoomer({
+ svg: svg,
+ zoomLayer: zoomLayer
+ });
+ expect(zoomer.scaleExtent()).toEqual([0.25, 10]);
+ });
+
+ it('should allow us to override the minimum zoom', function () {
+ zoomer = zs.createZoomer({
+ svg: svg,
+ zoomLayer: zoomLayer,
+ zoomMin: 1.23
+ });
+ expect(zoomer.scaleExtent()).toEqual([1.23, 10]);
+ });
+
+ it('should allow us to override the maximum zoom', function () {
+ zoomer = zs.createZoomer({
+ svg: svg,
+ zoomLayer: zoomLayer,
+ zoomMax: 13
+ });
+ expect(zoomer.scaleExtent()).toEqual([0.25, 13]);
+ });
+
+ // TODO: test zoomed() where we fake out the d3.event.sourceEvent etc...
+ // need to check default enabled (true) and custom enabled predicate
+ // need to check that the callback is invoked also
+
+ it('should invoke the callback on programmatic pan/zoom', function () {
+ var foo = { cb: function () {} };
+ spyOn(foo, 'cb');
+
+ zoomer = zs.createZoomer({
+ svg: svg,
+ zoomLayer: zoomLayer,
+ zoomCallback: foo.cb
+ });
+
+ zoomer.panZoom([0,0], 2);
+ expect(foo.cb).toHaveBeenCalled();
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
new file mode 100644
index 00000000..c9f3bef6
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js
@@ -0,0 +1,446 @@
+/*
+ * Copyright 2014,2015 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
+ */
+describe('factory: fw/util/fn.js', function() {
+ var $window,
+ fs,
+ someFunction = function () {},
+ someArray = [1, 2, 3],
+ someObject = { foo: 'bar'},
+ someNumber = 42,
+ someString = 'xyyzy',
+ someDate = new Date(),
+ stringArray = ['foo', 'bar'];
+
+ beforeEach(module('onosUtil'));
+
+ var mockWindow = {
+ innerWidth: 400,
+ innerHeight: 200,
+ navigator: {
+ userAgent: 'defaultUA'
+ }
+ };
+
+ beforeEach(function () {
+ module(function ($provide) {
+ $provide.value('$window', mockWindow);
+ });
+ });
+
+ beforeEach(inject(function (_$window_, FnService) {
+ $window = _$window_;
+ 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();
+ });
+
+ // === Tests for areFunctions()
+ it('areFunctions(): false for non-array', function () {
+ expect(fs.areFunctions({}, 'not-an-array')).toBeFalsy();
+ });
+ it('areFunctions(): true for empty-array', function () {
+ expect(fs.areFunctions({}, [])).toBeTruthy();
+ });
+ it('areFunctions(): true for some api', function () {
+ expect(fs.areFunctions({
+ a: function () {},
+ b: function () {}
+ }, ['b', 'a'])).toBeTruthy();
+ });
+ it('areFunctions(): false for some other api', function () {
+ expect(fs.areFunctions({
+ a: function () {},
+ b: 'not-a-function'
+ }, ['b', 'a'])).toBeFalsy();
+ });
+ it('areFunctions(): extraneous stuff NOT ignored', function () {
+ expect(fs.areFunctions({
+ a: function () {},
+ b: function () {},
+ c: 1,
+ d: 'foo'
+ }, ['a', 'b'])).toBeFalsy();
+ });
+ it('areFunctions(): extraneous stuff ignored (alternate fn)', function () {
+ expect(fs.areFunctionsNonStrict({
+ a: function () {},
+ b: function () {},
+ c: 1,
+ d: 'foo'
+ }, ['a', 'b'])).toBeTruthy();
+ });
+
+ // == use the now-tested areFunctions() on our own api:
+ it('should define api functions', function () {
+ expect(fs.areFunctions(fs, [
+ 'isF', 'isA', 'isS', 'isO', 'contains',
+ 'areFunctions', 'areFunctionsNonStrict', 'windowSize', 'isMobile',
+ 'find', 'inArray', 'removeFromArray', 'isEmptyObject', 'cap',
+ 'noPx', 'noPxStyle', 'endsWith', 'parseBitRate'
+ ])).toBeTruthy();
+ });
+
+
+ // === Tests for windowSize()
+ it('windowSize(): noargs', function () {
+ var dim = fs.windowSize();
+ expect(dim.width).toEqual(400);
+ expect(dim.height).toEqual(200);
+ });
+
+ it('windowSize(): adjust height', function () {
+ var dim = fs.windowSize(50);
+ expect(dim.width).toEqual(400);
+ expect(dim.height).toEqual(150);
+ });
+
+ it('windowSize(): adjust width', function () {
+ var dim = fs.windowSize(0, 50);
+ expect(dim.width).toEqual(350);
+ expect(dim.height).toEqual(200);
+ });
+
+ it('windowSize(): adjust width and height', function () {
+ var dim = fs.windowSize(101, 201);
+ expect(dim.width).toEqual(199);
+ expect(dim.height).toEqual(99);
+ });
+
+ // === Tests for isMobile()
+ var uaMap = {
+ chrome: "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_10_2) " +
+ "AppleWebKit/537.36 (KHTML, like Gecko) " +
+ "Chrome/41.0.2272.89 Safari/537.36",
+
+ iPad: "Mozilla/5.0 (iPad; CPU OS 7_0 like Mac OS X) " +
+ "AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 " +
+ "Mobile/11A465 Safari/9537.53",
+
+ iPhone: "Mozilla/5.0 (iPhone; CPU iPhone OS 7_0 like Mac OS X) " +
+ "AppleWebKit/537.51.1 (KHTML, like Gecko) Version/7.0 " +
+ "Mobile/11A465 Safari/9537.53"
+ };
+
+ function setUa(key) {
+ var str = uaMap[key];
+ expect(str).toBeTruthy();
+ mockWindow.navigator.userAgent = str;
+ }
+
+ it('isMobile(): should be false for Chrome on Mac OS X', function () {
+ setUa('chrome');
+ expect(fs.isMobile()).toBe(false);
+ });
+ it('isMobile(): should be true for Safari on iPad', function () {
+ setUa('iPad');
+ expect(fs.isMobile()).toBe(true);
+ });
+ it('isMobile(): should be true for Safari on iPhone', function () {
+ setUa('iPhone');
+ expect(fs.isMobile()).toBe(true);
+ });
+
+ // === Tests for find()
+ var dataset = [
+ { id: 'foo', name: 'Furby'},
+ { id: 'bar', name: 'Barbi'},
+ { id: 'baz', name: 'Basil'},
+ { id: 'goo', name: 'Gabby'},
+ { id: 'zoo', name: 'Zevvv'}
+ ];
+
+ it('should not find ooo', function () {
+ expect(fs.find('ooo', dataset)).toEqual(-1);
+ });
+ it('should find foo', function () {
+ expect(fs.find('foo', dataset)).toEqual(0);
+ });
+ it('should find zoo', function () {
+ expect(fs.find('zoo', dataset)).toEqual(4);
+ });
+
+ it('should not find Simon', function () {
+ expect(fs.find('Simon', dataset, 'name')).toEqual(-1);
+ });
+ it('should find Furby', function () {
+ expect(fs.find('Furby', dataset, 'name')).toEqual(0);
+ });
+ it('should find Zevvv', function () {
+ expect(fs.find('Zevvv', dataset, 'name')).toEqual(4);
+ });
+
+
+ // === Tests for inArray()
+ var objRef = { x:1, y:2 },
+ array = [1, 3.14, 'hey', objRef, 'there', true],
+ array2 = ['b', 'a', 'd', 'a', 's', 's'];
+
+ it('should return -1 on non-arrays', function () {
+ expect(fs.inArray(1, {x:1})).toEqual(-1);
+ });
+ it('should not find HOO', function () {
+ expect(fs.inArray('HOO', array)).toEqual(-1);
+ });
+ it('should find 1', function () {
+ expect(fs.inArray(1, array)).toEqual(0);
+ });
+ it('should find pi', function () {
+ expect(fs.inArray(3.14, array)).toEqual(1);
+ });
+ it('should find hey', function () {
+ expect(fs.inArray('hey', array)).toEqual(2);
+ });
+ it('should find the object', function () {
+ expect(fs.inArray(objRef, array)).toEqual(3);
+ });
+ it('should find there', function () {
+ expect(fs.inArray('there', array)).toEqual(4);
+ });
+ it('should find true', function () {
+ expect(fs.inArray(true, array)).toEqual(5);
+ });
+
+ it('should find the first occurrence A', function () {
+ expect(fs.inArray('a', array2)).toEqual(1);
+ });
+ it('should find the first occurrence S', function () {
+ expect(fs.inArray('s', array2)).toEqual(4);
+ });
+ it('should not find X', function () {
+ expect(fs.inArray('x', array2)).toEqual(-1);
+ });
+
+ // === Tests for removeFromArray()
+ it('should ignore non-arrays', function () {
+ expect(fs.removeFromArray(1, {x:1})).toBe(false);
+ });
+ it('should keep the array the same, for non-match', function () {
+ var array = [1, 2, 3];
+ expect(fs.removeFromArray(4, array)).toBe(false);
+ expect(array).toEqual([1, 2, 3]);
+ });
+ it('should remove a value', function () {
+ var array = [1, 2, 3];
+ expect(fs.removeFromArray(2, array)).toBe(true);
+ expect(array).toEqual([1, 3]);
+ });
+ it('should remove the first occurrence', function () {
+ var array = ['x', 'y', 'z', 'z', 'y'];
+ expect(fs.removeFromArray('y', array)).toBe(true);
+ expect(array).toEqual(['x', 'z', 'z', 'y']);
+ expect(fs.removeFromArray('x', array)).toBe(true);
+ expect(array).toEqual(['z', 'z', 'y']);
+ });
+
+ // === Tests for isEmptyObject()
+ it('should return true if an object is empty', function () {
+ expect(fs.isEmptyObject({})).toBe(true);
+ });
+ it('should return false if an object is not empty', function () {
+ expect(fs.isEmptyObject({foo: 'bar'})).toBe(false);
+ });
+
+ // === Tests for cap()
+ it('should ignore non-alpha', function () {
+ expect(fs.cap('123')).toEqual('123');
+ });
+ it('should capitalize first char', function () {
+ expect(fs.cap('Foo')).toEqual('Foo');
+ expect(fs.cap('foo')).toEqual('Foo');
+ expect(fs.cap('foo bar')).toEqual('Foo bar');
+ expect(fs.cap('FOO BAR')).toEqual('Foo bar');
+ expect(fs.cap('foo Bar')).toEqual('Foo bar');
+ });
+
+ // === Tests for noPx()
+ it('should return the value without px suffix', function () {
+ expect(fs.noPx('10px')).toBe(10);
+ expect(fs.noPx('500px')).toBe(500);
+ expect(fs.noPx('-80px')).toBe(-80);
+ });
+
+ // === Tests for noPxStyle()
+ it("should give a style's property without px suffix", function () {
+ var d3Elem = d3.select('body')
+ .append('div')
+ .attr('id', 'fooElem')
+ .style({
+ width: '500px',
+ height: '200px',
+ 'font-size': '12px'
+ });
+ expect(fs.noPxStyle(d3Elem, 'width')).toBe(500);
+ expect(fs.noPxStyle(d3Elem, 'height')).toBe(200);
+ expect(fs.noPxStyle(d3Elem, 'font-size')).toBe(12);
+ d3.select('#fooElem').remove();
+ });
+
+ // === Tests for endsWith()
+ it('should return true if string ends with foo', function () {
+ expect(fs.endsWith("barfoo", "foo")).toBe(true);
+ });
+
+ it('should return false if string doesnt end with foo', function () {
+ expect(fs.endsWith("barfood", "foo")).toBe(false);
+ });
+
+ // === Tests for parseBitRate()
+ it('should return 5 - a', function () {
+ expect(fs.parseBitRate('5.47 KBps')).toBe(5);
+ });
+
+ it('should return 5 - b', function () {
+ expect(fs.parseBitRate('5. KBps')).toBe(5);
+ });
+
+ it('should return 5 - c', function () {
+ expect(fs.parseBitRate('5 KBps')).toBe(5);
+ });
+
+ it('should return 5 - d', function () {
+ expect(fs.parseBitRate('5 Kbps')).toBe(5);
+ });
+
+ it('should return 2001', function () {
+ expect(fs.parseBitRate('2,001.59 Gbps')).toBe(2001);
+ });
+});
+
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/keys-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/keys-spec.js
new file mode 100644
index 00000000..227d7904
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/keys-spec.js
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2014,2015 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
+ */
+describe('factory: fw/util/keys.js', function() {
+ var $log, ks, fs, qhs,
+ d3Elem, elem, last;
+
+
+ beforeEach(module('onosUtil', 'onosSvg', 'onosLayer', 'onosNav'));
+
+ beforeEach(inject(function (_$log_, KeyService, FnService, QuickHelpService) {
+ $log = _$log_;
+ ks = KeyService;
+ fs = FnService;
+ qhs = QuickHelpService;
+ d3Elem = d3.select('body').append('p').attr('id', 'ptest');
+ elem = d3Elem.node();
+ ks.installOn(d3Elem);
+ ks.bindQhs(qhs);
+ last = {
+ view: null,
+ key: null,
+ code: null,
+ ev: null
+ };
+ }));
+
+ afterEach(function () {
+ d3.select('#ptest').remove();
+ });
+
+ it('should define the key service', function () {
+ expect(ks).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ks, [
+ 'bindQhs', 'installOn', 'keyBindings', 'gestureNotes', 'enableKeys'
+ ])).toBeTruthy();
+ });
+
+ // This no longer works because 'initKeyboardEvent' has been depreciated.
+ // Now there is a constructor for 'KeyboardEvent' where you can modify
+ // the new event with a dictionary(?) 'KeyboardEventInit'.
+ // However, the below code has been so recently depreciated, there are no
+ // examples online of how to use the new interface, and some browsers
+ // don't support it still. These tests will have to be put off for a
+ // while more. (Investigated 4/28/15)
+ // Also tried was Angular's triggerHandler() function, but it doesn't seem
+ // that it can take arguments about the event.
+ // Using jQuery in tests might be a good idea, for triggering test events.
+ 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);
+ }
+
+ // === 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();
+ }
+
+ // TODO: jsKeyDown(...) no longer emulates key presses ?! :(
+ // The following four unit tests ignored until we can figure this out.
+ // INVESTIGATED: see jsKeyDown for details...
+
+ xit('should allow specific key bindings', function () {
+ bindTestKeys();
+ verifyTestKeys();
+ });
+
+ xit('should allow specific key bindings with descriptions', function () {
+ bindTestKeys(true);
+ verifyTestKeys();
+ });
+
+ xit('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);
+ });
+
+ xit('should block keys when disabled', function () {
+ var cbCount = 0;
+
+ function cb() { cbCount++; }
+
+ function pressA() { jsKeyDown(elem, 65); } // 65 == 'A' keycode
+
+ ks.keyBindings({ A: cb });
+
+ expect(cbCount).toBe(0);
+
+ pressA();
+ expect(cbCount).toBe(1);
+
+ ks.enableKeys(false);
+ pressA();
+ expect(cbCount).toBe(1);
+
+ ks.enableKeys(true);
+ pressA();
+ expect(cbCount).toBe(2);
+ });
+
+ // === 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/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js
new file mode 100644
index 00000000..02152895
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2015 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 -- User Preference Service - Unit Tests
+ */
+describe('factory: fw/util/prefs.js', function() {
+ var $cookies, ps, fs;
+
+ beforeEach(module('onosUtil'));
+
+ var mockCookies = {
+ foo: 'bar'
+ };
+
+ beforeEach(function () {
+ module(function ($provide) {
+ $provide.value('$cookies', mockCookies);
+ });
+ });
+
+ beforeEach(inject(function (PrefsService, FnService, _$cookies_) {
+ ps = PrefsService;
+ fs = FnService;
+ $cookies = _$cookies_;
+ }));
+
+ it('should define PrefsService', function () {
+ expect(ps).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ps, [
+ 'getPrefs', 'asNumbers', 'setPrefs'
+ ])).toBe(true);
+ });
+
+ // === Tests for getPrefs()
+ // TODO unit tests for getPrefs()
+
+ // === Tests for asNumbers()
+ // TODO unit tests for asNumbers()
+
+ // === Tests for setPrefs()
+ // TODO unit tests for setPrefs()
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/random-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/random-spec.js
new file mode 100644
index 00000000..c4c61f1a
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/random-spec.js
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2014,2015 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 -- Random Service - Unit Tests
+ */
+describe('factory: fw/util/random.js', function() {
+ var rnd, $log, fs;
+
+ beforeEach(module('onosUtil'));
+
+ beforeEach(inject(function (RandomService, _$log_, FnService) {
+ rnd = RandomService;
+ $log = _$log_;
+ fs = FnService;
+ }));
+
+ // interesting use of a custom matcher...
+ beforeEach(function () {
+ jasmine.addMatchers({
+ toBeWithinOf: function () {
+ return {
+ compare: function (actual, distance, base) {
+ var lower = base - distance,
+ upper = base + distance,
+ result = {};
+
+ result.pass = Math.abs(actual - base) <= distance;
+
+ if (result.pass) {
+ // for negation with ".not"
+ result.message = 'Expected ' + actual +
+ ' to be outside ' + lower + ' and ' +
+ upper + ' (inclusive)';
+ } else {
+ result.message = 'Expected ' + actual +
+ ' to be between ' + lower + ' and ' +
+ upper + ' (inclusive)';
+ }
+ return result;
+ }
+ }
+ }
+ });
+ });
+
+ it('should define RandomService', function () {
+ expect(rnd).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(rnd, [
+ 'spread', 'randDim'
+ ])).toBeTruthy();
+ });
+
+ // really, can only do this heuristically.. hope this doesn't break
+ it('should spread results across the range', function () {
+ var load = 1000,
+ s = 12,
+ low = 0,
+ high = 0,
+ i, res,
+ which = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
+ minCount = load / s * 0.5; // generous error
+
+ for (i=0; i<load; i++) {
+ res = rnd.spread(s);
+ if (res < low) low = res;
+ if (res > high) high = res;
+ which[res + s/2]++;
+ }
+ expect(low).toBe(-6);
+ expect(high).toBe(5);
+
+ // check we got a good number of hits in each bucket
+ for (i=0; i<s; i++) {
+ expect(which[i]).toBeGreaterThan(minCount);
+ }
+ });
+
+ // really, can only do this heuristically.. hope this doesn't break
+ it('should choose results across the dimension', function () {
+ var load = 1000,
+ dim = 100,
+ low = 999,
+ high = 0,
+ i, res;
+
+ for (i=0; i<load; i++) {
+ res = rnd.randDim(dim);
+ if (res < low) low = res;
+ if (res > high) high = res;
+ expect(res).toBeWithinOf(36, 50);
+ }
+ });
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js
new file mode 100644
index 00000000..1d400ed1
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js
@@ -0,0 +1,162 @@
+/*
+ * Copyright 2014,2015 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 -- Theme Service - Unit Tests
+ */
+describe('factory: fw/util/theme.js', function() {
+ var ts, $log, fs;
+
+ beforeEach(module('onosUtil'));
+
+ beforeEach(inject(function (ThemeService, _$log_, FnService) {
+ ts = ThemeService;
+ $log = _$log_;
+ fs = FnService;
+ ts.init();
+ }));
+
+ it('should define ThemeService', function () {
+ expect(ts).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ts, [
+ 'init', 'theme', 'toggleTheme', 'addListener', 'removeListener'
+ ])).toBeTruthy();
+ });
+
+
+ function verifyBodyClass(yes, no) {
+ function bodyHasClass(c) {
+ return d3.select('body').classed(c);
+ }
+ expect(bodyHasClass(yes)).toBeTruthy();
+ expect(bodyHasClass(no)).toBeFalsy();
+ }
+
+ it('should default to light theme', function () {
+ expect(ts.theme()).toEqual('light');
+ });
+
+ it('should toggle to dark, then to light again', function () {
+ // Note: re-work this once theme-change listeners are implemented
+ spyOn($log, 'debug');
+
+ expect(ts.toggleTheme()).toEqual('dark');
+ expect(ts.theme()).toEqual('dark');
+ expect($log.debug).toHaveBeenCalledWith('Theme-Change-(toggle): dark');
+ verifyBodyClass('dark', 'light');
+
+ expect(ts.toggleTheme()).toEqual('light');
+ expect(ts.theme()).toEqual('light');
+ expect($log.debug).toHaveBeenCalledWith('Theme-Change-(toggle): light');
+ verifyBodyClass('light', 'dark');
+ });
+
+ it('should let us set the theme by name', function () {
+ // Note: re-work this once theme-change listeners are implemented
+ spyOn($log, 'debug');
+
+ expect(ts.theme()).toEqual('light');
+ ts.theme('dark');
+ expect(ts.theme()).toEqual('dark');
+ expect($log.debug).toHaveBeenCalledWith('Theme-Change-(set): dark');
+ verifyBodyClass('dark', 'light');
+ });
+
+ it('should ignore unknown theme names', function () {
+ // Note: re-work this once theme-change listeners are implemented
+ spyOn($log, 'debug');
+
+ expect(ts.theme()).toEqual('light');
+ verifyBodyClass('light', 'dark');
+
+ ts.theme('turquoise');
+ expect(ts.theme()).toEqual('light');
+ expect($log.debug).not.toHaveBeenCalled();
+ verifyBodyClass('light', 'dark');
+ });
+
+
+ // === Unit Tests for listeners
+
+ it('should report lack of callback', function () {
+ spyOn($log, 'error');
+ var list = ts.addListener();
+ expect($log.error).toHaveBeenCalledWith(
+ 'ThemeService.addListener(): callback not a function'
+ );
+ expect(list.error).toEqual('No callback defined');
+ });
+
+ it('should report non-functional callback', function () {
+ spyOn($log, 'error');
+ var list = ts.addListener(['some array']);
+ expect($log.error).toHaveBeenCalledWith(
+ 'ThemeService.addListener(): callback not a function'
+ );
+ expect(list.error).toEqual('No callback defined');
+ });
+
+ it('should invoke our callback with an event', function () {
+ var event;
+
+ function cb(ev) {
+ event = ev;
+ }
+
+ expect(event).toBeUndefined();
+ ts.addListener(cb);
+ ts.theme('dark');
+ expect(event).toEqual({
+ event: 'themeChange',
+ value: 'dark'
+ });
+ });
+
+ it('should invoke our callback at appropriate times', function () {
+ var calls = [],
+ phase,
+ listener;
+
+ function cb() {
+ calls.push(phase);
+ }
+
+ expect(calls).toEqual([]);
+
+ phase = 'pre';
+ ts.toggleTheme(); // -> dark
+
+ phase = 'added';
+ listener = ts.addListener(cb);
+ ts.toggleTheme(); // -> light
+
+ phase = 'same';
+ ts.theme('light'); // (still light - no event)
+
+ phase = 'diff';
+ ts.theme('dark'); // -> dark
+
+ phase = 'post';
+ ts.removeListener(listener);
+ ts.toggleTheme(); // -> light
+
+ expect(calls).toEqual(['added', 'diff']);
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/button-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/button-spec.js
new file mode 100644
index 00000000..5226984d
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/button-spec.js
@@ -0,0 +1,300 @@
+/*
+ * Copyright 2015 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 -- Widget -- Button Service - Unit Tests
+ */
+describe('factory: fw/widget/button.js', function () {
+ var $log, fs, bns, d3Elem;
+
+ beforeEach(module('onosWidget', 'onosSvg'));
+
+ beforeEach(inject(function (_$log_, FnService, ButtonService) {
+ $log = _$log_;
+ fs = FnService;
+ bns = ButtonService;
+ }));
+
+ beforeEach(function () {
+ d3Elem = d3.select('body').append('div').attr('id', 'testDiv');
+ });
+
+ afterEach(function () {
+ d3.select('#testDiv').remove();
+ });
+
+
+ // re-usable null function
+ function nullFunc () {}
+
+ it('should define ButtonService', function () {
+ expect(bns).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(bns, [
+ 'button', 'toggle', 'radioSet'
+ ])).toBeTruthy();
+ });
+
+ it('should verify button glyph', function () {
+ var btn = bns.button(d3Elem, 'foo-id', 'crown', nullFunc);
+ var el = d3Elem.select('#foo-id');
+ expect(el.classed('button')).toBeTruthy();
+ expect(el.attr('id')).toBe('foo-id');
+ expect(el.select('svg')).toBeTruthy();
+ expect(el.select('use')).toBeTruthy();
+ expect(el.select('use').classed('glyph')).toBeTruthy();
+ expect(el.select('use').attr('xlink:href')).toBe('#crown');
+ });
+
+
+ it('should not append button to an undefined div', function () {
+ spyOn($log, 'warn');
+ expect(bns.button(null, 'id', 'gid', nullFunc)).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('div undefined (button)');
+ });
+
+ it('should verify button callback', function () {
+ var count = 0;
+ function cb() { count++; }
+ var btn = bns.button(d3Elem, 'test', 'nothing', cb);
+ expect(count).toBe(0);
+ d3Elem.select('#test').on('click')();
+ expect(count).toBe(1);
+ });
+
+ it('should ignore non-function callbacks button', function () {
+ var count = 0;
+ var btn = bns.button(d3Elem, 'test', 'nothing', 'foo');
+ expect(count).toBe(0);
+ d3Elem.select('#test').on('click')();
+ expect(count).toBe(0);
+ });
+
+ it('should not append toggle to an undefined div', function () {
+ spyOn($log, 'warn');
+ expect(bns.toggle(undefined, 'id', 'gid', false, nullFunc)).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('div undefined (toggle button)');
+ });
+
+ it('should verify toggle glyph', function () {
+ var tog = bns.toggle(d3Elem, 'foo-id', 'crown', false, nullFunc);
+ var el = d3Elem.select('#foo-id');
+ expect(el.classed('toggleButton')).toBeTruthy();
+ expect(el.attr('id')).toBe('foo-id');
+ expect(el.select('svg')).toBeTruthy();
+ expect(el.select('use')).toBeTruthy();
+ expect(el.select('use').classed('glyph')).toBeTruthy();
+ expect(el.select('use').attr('xlink:href')).toBe('#crown');
+ });
+
+ it('should toggle the selected state', function () {
+ var tog = bns.toggle(d3Elem, 'test', 'nothing');
+ expect(tog.selected()).toBe(false);
+ tog.toggle();
+ expect(tog.selected()).toBe(true);
+ tog.toggle();
+ expect(tog.selected()).toBe(false);
+ });
+
+ it('should set toggle state', function () {
+ var tog = bns.toggle(d3Elem, 'test', 'nothing');
+ tog.toggle(true);
+ expect(tog.selected()).toBe(true);
+ tog.toggle();
+ expect(tog.selected()).toBe(false);
+ tog.toggle('truthy string');
+ expect(tog.selected()).toBe(true);
+ tog.toggle(null);
+ expect(tog.selected()).toBe(false);
+ tog.toggle('');
+ expect(tog.selected()).toBe(false);
+ });
+
+ it('should verity toggle initial state', function () {
+ var tog = bns.toggle(d3Elem, 'id', 'gid', true);
+ expect(tog.selected()).toBe(true);
+ tog = bns.toggle(d3Elem, 'id', 'gid', false);
+ expect(tog.selected()).toBe(false);
+ tog = bns.toggle(d3Elem, 'id', 'gid', '');
+ expect(tog.selected()).toBe(false);
+ tog = bns.toggle(d3Elem, 'id', 'gid', 'something');
+ expect(tog.selected()).toBe(true);
+ });
+
+ it('should not append radio button set to an undefined div', function () {
+ spyOn($log, 'warn');
+ expect(bns.radioSet(undefined, 'id', [])).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('div undefined (radio button set)');
+ });
+
+ it('should not create radio button set from a non-array', function () {
+ var rads = {test: 'test'};
+ var warning = 'invalid array (radio button set)';
+
+ spyOn($log, 'warn');
+
+ expect(bns.radioSet(d3Elem, 'test', rads)).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(warning);
+ rads = 'rads';
+ expect(bns.radioSet(d3Elem, 'test', rads)).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(warning);
+ rads = {arr: [1, 2, 3]};
+ expect(bns.radioSet(d3Elem, 'test', rads)).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith(warning);
+ });
+
+ it('should not create radio button set from empty array', function () {
+ var rads = [];
+ spyOn($log, 'warn');
+ expect(bns.radioSet(d3Elem, 'test', rads)).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('invalid array (radio button set)');
+ });
+
+ it('should verify radio button glyph structure', function () {
+ var rads = [
+ { gid: 'crown', cb: nullFunc, tooltip: 'n/a'}
+ ], rdiv;
+
+ spyOn($log, 'warn');
+ expect(bns.radioSet(d3Elem, 'foo', rads)).toBeTruthy();
+ expect($log.warn).not.toHaveBeenCalled();
+
+ rdiv = d3Elem.select('div');
+ expect(rdiv.classed('radioSet')).toBe(true);
+ expect(rdiv.select('div').classed('radioButton')).toBe(true);
+ expect(rdiv.select('div').attr('id')).toBe('foo-0');
+ expect(rdiv.select('div').select('svg')).toBeTruthy();
+ expect(rdiv.select('use').classed('glyph')).toBeTruthy();
+ expect(rdiv.select('use').attr('xlink:href')).toBe('#crown');
+ });
+
+ it('should verify more than one radio button glyph was added', function () {
+ var rads = [
+ { gid: 'crown', cb: nullFunc, tooltip: 'n/a'},
+ { gid: 'router', cb: nullFunc, tooltip: 'n/a'}
+ ], rdiv;
+
+ expect(bns.radioSet(d3Elem, 'foo', rads)).toBeTruthy();
+ rdiv = d3Elem.select('div');
+ expect(rdiv.select('#foo-0')).toBeTruthy();
+ expect(rdiv.select('#foo-1')).toBeTruthy();
+
+ expect(rdiv.select('#foo-0')
+ .select('use')
+ .classed('glyph'))
+ .toBeTruthy();
+ expect(rdiv.select('#foo-0')
+ .select('use')
+ .attr('xlink:href'))
+ .toBe('#crown');
+
+ expect(rdiv.select('#foo-1')
+ .select('use')
+ .classed('glyph'))
+ .toBeTruthy();
+ expect(rdiv.select('#foo-1')
+ .select('use')
+ .attr('xlink:href'))
+ .toBe('#router');
+ });
+
+ it('should select radio button by index', function () {
+ var count0 = 0,
+ count1 = 9;
+ function cb0() { count0++; }
+ function cb1() { count1++; }
+
+ function validate(expSel, exp0, exp1) {
+ expect(rset.selected()).toBe(expSel);
+ expect(count0).toBe(exp0);
+ expect(count1).toBe(exp1);
+ }
+
+ function checkWarn(msg, index) {
+ expect($log.warn).toHaveBeenCalledWith(msg, index);
+ }
+
+ var rads = [
+ { gid: 'crown', cb: cb0, tooltip: 'n/a'},
+ { gid: 'router', cb: cb1, tooltip: 'n/a'}
+ ],
+ rset = bns.radioSet(d3Elem, 'test', rads);
+ spyOn($log, 'warn');
+
+ validate(0, 0, 9);
+ rset.selectedIndex(0);
+ validate(0, 0, 9);
+
+ rset.selectedIndex(1);
+ validate(1, 0, 10);
+
+ rset.selectedIndex(-1);
+ checkWarn('invalid radio button index:', -1);
+ validate(1, 0, 10);
+
+ rset.selectedIndex(66);
+ checkWarn('invalid radio button index:', 66);
+ validate(1, 0, 10);
+
+ rset.selectedIndex(0);
+ validate(0, 1, 10);
+ });
+
+ it('should select radio button by key', function () {
+ var count0 = 0,
+ count1 = 9;
+ function cb0() { count0++; }
+ function cb1() { count1++; }
+
+ function validate(expSel, exp0, exp1) {
+ expect(rset.selected()).toBe(expSel);
+ expect(count0).toBe(exp0);
+ expect(count1).toBe(exp1);
+ }
+
+ function checkWarn(msg, index) {
+ expect($log.warn).toHaveBeenCalledWith(msg, index);
+ }
+
+ var rads = [
+ { key: 'foo', gid: 'crown', cb: cb0, tooltip: 'n/a'},
+ { key: 'bar', gid: 'router', cb: cb1, tooltip: 'n/a'}
+ ],
+ rset = bns.radioSet(d3Elem, 'test', rads);
+ spyOn($log, 'warn');
+
+ validate('foo', 0, 9);
+ rset.selected('foo');
+ validate('foo', 0, 9);
+
+ rset.selected('bar');
+ validate('bar', 0, 10);
+
+ rset.selected('blob');
+ checkWarn('no radio button with key:', 'blob');
+ validate('bar', 0, 10);
+
+ rset.selected('foo');
+ validate('foo', 1, 10);
+
+ rset.selected('foo');
+ validate('foo', 1, 10);
+ checkWarn('current index already selected:', 0);
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js
new file mode 100644
index 00000000..4f8f0c0f
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js
@@ -0,0 +1,340 @@
+/*
+ * Copyright 2015 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 -- Widget -- Table Service - Unit Tests
+ */
+describe('factory: fw/widget/table.js', function () {
+ var $log, $compile, $rootScope,
+ fs, ts, mast, is,
+ scope,
+ containerDiv,
+ headerDiv, bodyDiv,
+ header, body,
+ mockHeader,
+ mockHeaderHeight = 40;
+
+ var onosFixedHeaderTags =
+ '<div class="summary-list" onos-fixed-header>' +
+ '<div class="table-header">' +
+ '<table>' +
+ '<tr>' +
+ '<td colId="type" class="table-icon"></td>' +
+ '<td colId="id">Host ID </td>' +
+ '<td colId="mac" sortable>MAC Address </td>' +
+ '<td colId="location" col-width="110px">Location </td>' +
+ '</tr>' +
+ '</table>' +
+ '</div>' +
+
+ '<div class="table-body">' +
+ '<table>' +
+ '<tr class="ignore-width">' +
+ '<td class="not-picked"></td>' +
+ '</tr>' +
+ '<tr>' +
+ '<td class="table-icon">Some Icon</td>' +
+ '<td>Some ID</td>' +
+ '<td>Some MAC Address</td>' +
+ '<td>Some Location</td>' +
+ '</tr>' +
+ '</table>' +
+ '</div>' +
+ '</div>',
+
+ onosSortableHeaderTags =
+ '<div onos-sortable-header ' +
+ 'sort-callback="sortCallback(requestParams)">' +
+ '<table>' +
+ '<tr>' +
+ '<td colId="type"></td>' +
+ '<td colId="id" sortable>Host ID </td>' +
+ '<td colId="mac" sortable>MAC Address </td>' +
+ '<td colId="location" sortable>Location </td>' +
+ '</tr>' +
+ '</table>' +
+ '</div>';
+
+ beforeEach(module('onosWidget', 'onosUtil', 'onosMast', 'onosSvg'));
+
+ var mockWindow = {
+ innerWidth: 600,
+ innerHeight: 400,
+ navigator: {
+ userAgent: 'defaultUA'
+ },
+ on: function () {},
+ addEventListener: function () {}
+ };
+
+ beforeEach(function () {
+ module(function ($provide) {
+ $provide.value('$window', mockWindow);
+ });
+ });
+
+ beforeEach(inject(function (_$log_, _$compile_, _$rootScope_,
+ FnService, TableService, MastService, IconService) {
+ $log = _$log_;
+ $compile = _$compile_;
+ $rootScope = _$rootScope_;
+ fs = FnService;
+ ts = TableService;
+ mast = MastService;
+ is = IconService;
+ }));
+
+ beforeEach(function () {
+ scope = $rootScope.$new();
+ scope.tableData = [];
+ });
+
+ // Note: dummy header so that d3 doesn't trip up.
+ // $compile has to be used on the directive tag element, so it can't
+ // be included in the tag strings declared above.
+ beforeEach(function () {
+ mockHeader = d3.select('body')
+ .append('h2')
+ .classed('tabular-header', true)
+ .style({
+ height: mockHeaderHeight + 'px',
+ margin: 0,
+ padding: 0
+ })
+ .text('Some Header');
+ });
+
+ afterEach(function () {
+ containerDiv = undefined;
+ headerDiv = undefined;
+ bodyDiv = undefined;
+ header = undefined;
+ body = undefined;
+ mockHeader.remove();
+ });
+
+ function populateTableData() {
+ scope.tableData = [
+ {
+ type: 'endstation',
+ id: '1234',
+ mac: '00:00:03',
+ location: 'USA'
+ }
+ ];
+ }
+
+ it('should define TableBuilderService', function () {
+ expect(ts).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ts, [
+ 'resetSortIcons'
+ ])).toBeTruthy();
+ });
+
+ function compile(elem) {
+ var compiled = $compile(elem);
+ compiled(scope);
+ scope.$digest();
+ }
+
+ function selectTables() {
+ expect(containerDiv.find('div').length).toBe(2);
+
+ headerDiv = angular.element(containerDiv[0].querySelector('.table-header'));
+ expect(headerDiv.length).toBe(1);
+
+ bodyDiv = angular.element(containerDiv[0].querySelector('.table-body'));
+ expect(bodyDiv.length).toBe(1);
+
+ header = headerDiv.find('table');
+ expect(header.length).toBe(1);
+
+ body = bodyDiv.find('table');
+ expect(body.length).toBe(1);
+ }
+
+ function verifyGivenTags(dirName, div) {
+ expect(div).toBeDefined();
+ expect(div.attr(dirName)).toBe('');
+ }
+
+ function verifyDefaultSize() {
+ expect(header.css('width')).toBe('570px');
+ expect(body.css('width')).toBe('570px');
+ }
+
+ function verifyHeight() {
+ var padding = 22,
+ mastHeight = 36,
+ tableHeight = (mockWindow.innerHeight - mockHeaderHeight) -
+ (fs.noPx(headerDiv.css('height')) + mastHeight + padding);
+
+ expect(bodyDiv.css('height')).toBe(tableHeight + 'px');
+ }
+
+ function verifyColWidth() {
+ var hdrs = header.find('td'),
+ cols = body.find('td');
+
+ expect(angular.element(hdrs[0]).css('width')).toBe('33px');
+ expect(angular.element(hdrs[3]).css('width')).toBe('110px');
+
+ expect(angular.element(cols[1]).css('width')).toBe('33px');
+ expect(angular.element(cols[4]).css('width')).toBe('110px');
+ }
+
+ function verifyCallbacks(h) {
+ expect(scope.sortCallback).not.toHaveBeenCalled();
+
+ h[0].click();
+ expect(scope.sortCallback).not.toHaveBeenCalled();
+
+ h[1].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'id',
+ sortDir: 'asc'
+ });
+ h[1].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'id',
+ sortDir: 'desc'
+ });
+ h[1].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'id',
+ sortDir: 'asc'
+ });
+
+ h[2].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'mac',
+ sortDir: 'asc'
+ });
+ h[2].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'mac',
+ sortDir: 'desc'
+ });
+ h[2].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'mac',
+ sortDir: 'asc'
+ });
+
+ h[3].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'location',
+ sortDir: 'asc'
+ });
+ h[3].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'location',
+ sortDir: 'desc'
+ });
+ h[3].click();
+ expect(scope.sortCallback).toHaveBeenCalledWith({
+ sortCol: 'location',
+ sortDir: 'asc'
+ });
+ }
+
+ function verifyIcons(h) {
+ var currH, div;
+
+ h[1].click();
+ currH = angular.element(h[1]);
+ div = currH.find('div');
+ expect(div.html()).toBe(
+ '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
+ '50 50"><g class="icon upArrow"><rect width="50" height="50" ' +
+ 'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
+ 'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleUp">' +
+ '</use></g></svg>'
+ );
+ h[1].click();
+ div = currH.find('div');
+ expect(div.html()).toBe(
+ '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
+ '50 50"><g class="icon downArrow"><rect width="50" height="50" ' +
+ 'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
+ 'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleDown">' +
+ '</use></g></svg>'
+ );
+
+ h[2].click();
+ div = currH.children();
+ // clicked on a new element, so the previous icon should have been deleted
+ expect(div.html()).toBeFalsy();
+
+ // the new element should have the ascending icon
+ currH = angular.element(h[2]);
+ div = currH.children();
+ expect(div.html()).toBe(
+ '<svg class="embeddedIcon" width="10" height="10" viewBox="0 0 ' +
+ '50 50"><g class="icon upArrow"><rect width="50" height="50" ' +
+ 'rx="5"></rect><use width="50" height="50" class="glyph" xmlns:' +
+ 'xlink="http://www.w3.org/1999/xlink" xlink:href="#triangleUp">' +
+ '</use></g></svg>'
+ );
+ }
+
+ it('should affirm that onos-fixed-header is working', function () {
+ containerDiv = angular.element(onosFixedHeaderTags);
+
+ compile(containerDiv);
+
+ verifyGivenTags('onos-fixed-header', containerDiv);
+ selectTables();
+ verifyDefaultSize();
+
+ populateTableData();
+
+ scope.$emit('LastElement');
+ scope.$digest();
+
+ verifyHeight();
+ verifyColWidth();
+
+ mockWindow.innerHeight = 300;
+ scope.$digest();
+ verifyHeight();
+
+ mockWindow.innerWidth = 500;
+ scope.$digest();
+ verifyColWidth();
+ });
+
+ it('should affirm that onos-sortable-header is working', function () {
+ headerDiv = angular.element(onosSortableHeaderTags);
+
+ compile(headerDiv);
+ verifyGivenTags('onos-sortable-header', headerDiv);
+
+ scope.sortCallback = jasmine.createSpy('sortCallback');
+
+ header = headerDiv.find('td');
+ verifyCallbacks(header);
+ verifyIcons(header);
+ });
+
+ // Note: testing resetSortIcons isn't feasible because due to the nature of
+ // directive compilation: they are jQuery elements, not d3 elements,
+ // so the function doesn't work.
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js
new file mode 100644
index 00000000..0372a25f
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2015 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 -- Widget -- Table Builder Service - Unit Tests
+ */
+
+describe('factory: fw/widget/tableBuilder.js', function () {
+ var $log, $rootScope, fs, tbs, is;
+
+ var mockObj,
+ mockWss = {
+ bindHandlers: function () {},
+ sendEvent: function () {},
+ unbindHandlers: function () {}
+ };
+
+ beforeEach(module('onosWidget', 'onosUtil', 'onosRemote', 'onosSvg'));
+
+ beforeEach(function () {
+ module(function ($provide) {
+ $provide.value('WebSocketService', mockWss);
+ });
+ });
+
+ beforeEach(inject(function (_$log_, _$rootScope_,
+ FnService, TableBuilderService, IconService) {
+ $log = _$log_;
+ $rootScope = _$rootScope_;
+ fs = FnService;
+ tbs = TableBuilderService;
+ is = IconService;
+ }));
+
+ function mockSelCb(event, sel) {}
+
+ beforeEach(function () {
+ mockObj = {
+ scope: $rootScope.$new(),
+ tag: 'foo',
+ selCb: mockSelCb
+ };
+ });
+
+ afterEach(function () {
+ mockObj = {};
+ });
+
+ it('should define TableBuilderService', function () {
+ expect(tbs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tbs, [
+ 'buildTable'
+ ])).toBeTruthy();
+ });
+
+ it('should verify sortCb', function () {
+ spyOn(mockWss, 'sendEvent');
+ expect(mockObj.scope.sortCallback).not.toBeDefined();
+ tbs.buildTable(mockObj);
+ expect(mockObj.scope.sortCallback).toBeDefined();
+ expect(mockWss.sendEvent).toHaveBeenCalled();
+ });
+
+ it('should set tableData', function () {
+ expect(mockObj.scope.tableData).not.toBeDefined();
+ tbs.buildTable(mockObj);
+ expect(fs.isA(mockObj.scope.tableData)).toBeTruthy();
+ expect(mockObj.scope.tableData.length).toBe(0);
+ });
+
+ it('should unbind handlers on destroyed scope', function () {
+ spyOn(mockWss, 'unbindHandlers');
+ tbs.buildTable(mockObj);
+ expect(mockWss.unbindHandlers).not.toHaveBeenCalled();
+ mockObj.scope.$destroy();
+ expect(mockWss.unbindHandlers).toHaveBeenCalled();
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/toolbar-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/toolbar-spec.js
new file mode 100644
index 00000000..83fd042e
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/toolbar-spec.js
@@ -0,0 +1,180 @@
+/*
+ * Copyright 2015 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 -- Widget -- Toolbar Service - Unit Tests
+ */
+describe('factory: fw/widget/toolbar.js', function () {
+ var $log, fs, tbs, ps, bns, is;
+
+ beforeEach(module('onosWidget', 'onosUtil', 'onosLayer', 'onosSvg'));
+
+ beforeEach(inject(function (_$log_, FnService, ToolbarService,
+ PanelService, ButtonService, IconService) {
+ $log = _$log_;
+ fs = FnService;
+ tbs = ToolbarService;
+ ps = PanelService;
+ bns = ButtonService;
+ is = IconService;
+ }));
+
+ beforeEach(function () {
+ // panel service expects #floatpanels div into which panels are placed
+ d3.select('body').append('div').attr('id', 'floatpanels');
+ tbs.init();
+ ps.init();
+ });
+
+ afterEach(function () {
+ tbs.init();
+ ps.init();
+ d3.select('#floatpanels').remove();
+ });
+
+ function nullFunc() { }
+
+ it('should define ToolbarService', function () {
+ expect(tbs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tbs, [
+ 'init',
+ 'createToolbar', 'destroyToolbar'
+ ])).toBeTruthy();
+ });
+
+ it('should warn when no id is given', function () {
+ spyOn($log, 'warn');
+ expect(tbs.createToolbar()).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('createToolbar: ' +
+ 'no ID given: [undefined]');
+ });
+
+ it('should warn when a duplicate id is given', function () {
+ spyOn($log, 'warn');
+ expect(tbs.createToolbar('test')).toBeTruthy();
+ expect(tbs.createToolbar('test')).toBeNull();
+ expect($log.warn).toHaveBeenCalledWith('createToolbar: ' +
+ 'duplicate ID given: [test]');
+ });
+
+ it('should verify the toolbar arrow div exists', function () {
+ tbs.createToolbar('test');
+
+ // NOTE: toolbar service prefixes id with 'toolbar-'
+ var tbar = d3.select('#toolbar-test'),
+ arrow = tbar.select('.tbar-arrow');
+
+ expect(arrow.size()).toBe(1);
+ expect(arrow.select('svg').size()).toBe(1);
+ expect(arrow.select('svg').select('g').select('use')
+ .attr('xlink:href')).toEqual('#triangleUp');
+ });
+
+
+ it('should create a button', function () {
+ spyOn($log, 'warn');
+ var toolbar = tbs.createToolbar('foo'),
+ btn = toolbar.addButton('btn0', 'gid');
+ expect(btn).not.toBeNull();
+ expect(btn.id).toBe('toolbar-foo-btn0');
+ expect($log.warn).not.toHaveBeenCalled();
+ });
+
+ it('should not create an item with a duplicate id', function () {
+ spyOn($log, 'warn');
+ var toolbar = tbs.createToolbar('foo'),
+ btn = toolbar.addButton('btn0', 'gid'),
+ dup;
+ expect(btn).not.toBeNull();
+ expect(btn.id).toBe('toolbar-foo-btn0');
+
+ dup = toolbar.addButton('btn0', 'gid');
+ expect($log.warn).toHaveBeenCalledWith('addButton: duplicate ID:', 'btn0');
+ expect(dup).toBeNull();
+
+ dup = toolbar.addToggle('btn0', 'gid');
+ expect($log.warn).toHaveBeenCalledWith('addToggle: duplicate ID:', 'btn0');
+ expect(dup).toBeNull();
+
+ dup = toolbar.addRadioSet('btn0', []);
+ expect($log.warn).toHaveBeenCalledWith('addRadioSet: duplicate ID:', 'btn0');
+ expect(dup).toBeNull();
+ });
+
+ it('should create a toggle', function () {
+ spyOn($log, 'warn');
+ var toolbar = tbs.createToolbar('foo'),
+ tog = toolbar.addButton('tog0', 'gid');
+ expect(tog).not.toBeNull();
+ expect(tog.id).toBe('toolbar-foo-tog0');
+ expect($log.warn).not.toHaveBeenCalled();
+ });
+
+ it('should create a radio button set', function () {
+ spyOn($log, 'warn');
+ var toolbar = tbs.createToolbar('foo'),
+ rset = [
+ { gid: 'crown', cb: nullFunc, tooltip: 'A Crown' },
+ { gid: 'bird', cb: nullFunc, tooltip: 'A Bird' }
+ ],
+ rad = toolbar.addRadioSet('rad0', rset);
+ expect(rad).not.toBeNull();
+ expect(rad.selectedIndex()).toBe(0);
+ expect($log.warn).not.toHaveBeenCalled();
+ });
+
+ it('should create a separator div', function () {
+ spyOn($log, 'warn');
+ var toolbar = tbs.createToolbar('foo'),
+ tbar = d3.select('#toolbar-foo');
+
+ toolbar.addSeparator();
+ expect($log.warn).not.toHaveBeenCalled();
+
+ expect(tbar.select('.separator').size()).toBe(1);
+ });
+
+ it('should add another row of buttons', function () {
+ var toolbar = tbs.createToolbar('foo'),
+ tbar = d3.select('#toolbar-foo'),
+ rows;
+ toolbar.addButton('btn0', 'gid');
+ toolbar.addRow();
+ toolbar.addButton('btn1', 'gid');
+
+ rows = tbar.selectAll('.tbar-row');
+ expect(rows.size()).toBe(2);
+ rows.each(function (d, i) {
+ expect(d3.select(this)
+ .select('div')
+ .attr('id','toolbar-foo-btn' + i)
+ .empty())
+ .toBe(false);
+ });
+ });
+
+ it('should not add a row if current row is empty', function () {
+ var toolbar = tbs.createToolbar('foo');
+ expect(toolbar.addRow()).toBeNull();
+ toolbar.addButton('btn0', 'gid');
+ expect(toolbar.addRow()).not.toBeNull();
+ expect(toolbar.addRow()).toBeNull();
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tooltip-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tooltip-spec.js
new file mode 100644
index 00000000..0ae1f65d
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tooltip-spec.js
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2015 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 -- Widget -- Tooltip Service - Unit Tests
+ */
+describe('factory: fw/widget/tooltip.js', function () {
+ var $log, fs, tts, d3Elem;
+
+ beforeEach(module('onosWidget', 'onosUtil'));
+
+ beforeEach(inject(function (_$log_, FnService, TooltipService) {
+ $log = _$log_;
+ fs = FnService;
+ tts = TooltipService;
+ }));
+
+ beforeEach(function () {
+ d3Elem = d3.select('body').append('div').attr('id', 'tooltip');
+ });
+
+ afterEach(function () {
+ d3.select('#tooltip').remove();
+ });
+
+ it('should define TooltipService', function () {
+ expect(tts).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tts, [
+ 'showTooltip', 'cancelTooltip'
+ ])).toBeTruthy();
+ });
+
+ it('should not accept undefined arguments', function () {
+ var btn = d3.select('body').append('div');
+ expect(tts.showTooltip()).toBeFalsy();
+ expect(tts.showTooltip(btn)).toBeFalsy();
+
+ expect(tts.cancelTooltip()).toBeFalsy();
+ });
+
+ // TODO: figure out how to test this
+ // testing mouse events is tough
+ // showTooltip needs a d3 event, which currently has no test backend
+ // .each is a workaround, which provides this, d, and i
+ xit('should show a tooltip', function () {
+ var btn = d3.select('body').append('div').attr('id', 'foo');
+ btn.each(function () {
+ tts.showTooltip(this, 'yay a tooltip');
+ });
+ // tests here
+ });
+
+ // can't cancel a tooltip until we show one
+ // because currElemId isn't set otherwise
+ xit('should cancel an existing tooltip', function () {
+ var btn = d3.select('body').append('div').attr('id', 'foo');
+ btn.each(function () {
+ tts.cancelTooltip(this);
+ });
+ expect(d3Elem.text()).toBe('');
+ expect(d3Elem.style('display')).toBe('none');
+ });
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/onos-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/onos-spec.js
new file mode 100644
index 00000000..3bf0ce82
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/onos-spec.js
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2014,2015 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
+ */
+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.2.0', function () {
+ expect(ctrl.version).toEqual('1.2.0');
+ });
+}); \ No newline at end of file
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/device-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/device-spec.js
new file mode 100644
index 00000000..c4957843
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/device-spec.js
@@ -0,0 +1,38 @@
+/*
+ * Copyright 2015 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 -- Device Controller - Unit Tests
+ */
+describe('Controller: OvDeviceCtrl', function () {
+ var $log, $scope, $controller, ctrl, $mockHttp;
+
+ // instantiate the Device module
+ beforeEach(module('ovDevice', 'onosRemote', 'onosLayer', 'onosSvg',
+ 'onosNav', 'ngRoute'));
+
+ beforeEach(inject(function(_$log_, $rootScope, _$controller_, $httpBackend) {
+ $log = _$log_;
+ $scope = $rootScope.$new();
+ $controller = _$controller_;
+ $mockHttp = $httpBackend;
+ }));
+
+ beforeEach(function() {
+ ctrl = $controller('OvDeviceCtrl', { $scope: $scope });
+ });
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/fakeData.json b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/fakeData.json
new file mode 100644
index 00000000..a6bd78f0
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/fakeData.json
@@ -0,0 +1,88 @@
+{
+ "devices": [
+ {
+ "id": "of:0000000000000001",
+ "available": true,
+ "role": "MASTER",
+ "mfr": "Nicira, Inc.",
+ "hw": "Open vSwitch",
+ "sw": "2.0.1",
+ "serial": "None",
+ "annotations": {
+ "protocol": "OF_10"
+ }
+ },
+ {
+ "id": "of:0000000000000004",
+ "available": true,
+ "role": "MASTER",
+ "mfr": "Nicira, Inc.",
+ "hw": "Open vSwitch",
+ "sw": "2.0.1",
+ "serial": "None",
+ "annotations": {
+ "protocol": "OF_10"
+ }
+ },
+ {
+ "id": "of:0000000000000005",
+ "available": true,
+ "role": "MASTER",
+ "mfr": "Nicira, Inc.",
+ "hw": "Open vSwitch",
+ "sw": "2.0.1",
+ "serial": "None",
+ "annotations": {
+ "protocol": "OF_10"
+ }
+ },
+ {
+ "id": "of:0000000000000002",
+ "available": true,
+ "role": "MASTER",
+ "mfr": "Nicira, Inc.",
+ "hw": "Open vSwitch",
+ "sw": "2.0.1",
+ "serial": "None",
+ "annotations": {
+ "protocol": "OF_10"
+ }
+ },
+ {
+ "id": "of:0000000000000003",
+ "available": true,
+ "role": "MASTER",
+ "mfr": "Nicira, Inc.",
+ "hw": "Open vSwitch",
+ "sw": "2.0.1",
+ "serial": "None",
+ "annotations": {
+ "protocol": "OF_10"
+ }
+ },
+ {
+ "id": "of:0000000000000006",
+ "available": true,
+ "role": "MASTER",
+ "mfr": "Nicira, Inc.",
+ "hw": "Open vSwitch",
+ "sw": "2.0.1",
+ "serial": "None",
+ "annotations": {
+ "protocol": "OF_10"
+ }
+ },
+ {
+ "id": "of:0000000000000007",
+ "available": true,
+ "role": "MASTER",
+ "mfr": "Nicira, Inc.",
+ "hw": "Open vSwitch",
+ "sw": "2.0.1",
+ "serial": "None",
+ "annotations": {
+ "protocol": "OF_10"
+ }
+ }
+ ]
+} \ No newline at end of file
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoEvent-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoEvent-spec.js
new file mode 100644
index 00000000..ef94711e
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoEvent-spec.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Event Service - Unit Tests
+ */
+describe('factory: view/topo/topoEvent.js', function() {
+ var $log, fs, tes, bns;
+
+ beforeEach(module('ovTopo', 'onosNav', 'onosUtil', 'onosLayer', 'ngRoute',
+ 'onosWidget'));
+
+ beforeEach(inject(function (_$log_, FnService,
+ TopoEventService, ButtonService) {
+ $log = _$log_;
+ fs = FnService;
+ tes = TopoEventService;
+ bns = ButtonService;
+ }));
+
+ it('should define TopoEventService', function () {
+ expect(tes).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tes, [
+ 'start', 'stop'
+ ])).toBeTruthy();
+ });
+
+ // TODO: more tests...
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js
new file mode 100644
index 00000000..eebccfca
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Filter Service - Unit Tests
+ */
+describe('factory: view/topo/topoFilter.js', function() {
+ var $log, fs, fltr, bns, api;
+
+ var mockNodes = {
+ each: function () {},
+ classed: function () {}
+ },
+ mockLinks = {
+ each: function () {},
+ classed: function () {}
+ };
+
+ beforeEach(module('ovTopo', 'onosUtil', 'onosLayer', 'ngRoute', 'onosNav',
+ 'onosWidget'));
+
+ beforeEach(inject(function (_$log_, FnService,
+ TopoFilterService, ButtonService) {
+ $log = _$log_;
+ fs = FnService;
+ fltr = TopoFilterService;
+ bns = ButtonService;
+
+ api = {
+ node: function () { return mockNodes; },
+ link: function () { return mockLinks; }
+ };
+ }));
+
+ afterEach(function () {
+
+ });
+
+ it('should define TopoFilterService', function () {
+ expect(fltr).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(fltr, [
+ 'initFilter',
+ 'clickAction', 'selected', 'inLayer'
+ ])).toBeTruthy();
+ });
+
+ it('should report the selected button', function () {
+ fltr.initFilter(api);
+ expect(fltr.selected()).toEqual('all');
+ });
+
+ // TODO: test the on click functions
+
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js
new file mode 100644
index 00000000..edb1cc56
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Force Service - Unit Tests
+ */
+describe('factory: view/topo/topoForce.js', function() {
+ var $log, fs, tfs, bns;
+
+ beforeEach(module('ovTopo', 'onosUtil', 'onosLayer', 'ngRoute', 'onosNav',
+ 'onosWidget'));
+
+ beforeEach(inject(function (_$log_, FnService,
+ TopoForceService, ButtonService) {
+ $log = _$log_;
+ fs = FnService;
+ tfs = TopoForceService;
+ bns = ButtonService;
+ }));
+
+ it('should define TopoForceService', function () {
+ expect(tfs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tfs, [
+ 'initForce', 'newDim', 'destroyForce',
+
+ 'updateDeviceColors', 'toggleHosts',
+ 'togglePorts', 'toggleOffline',
+ 'cycleDeviceLabels', 'unpin', 'showMastership', 'showBadLinks',
+
+ 'addDevice', 'updateDevice', 'removeDevice',
+ 'addHost', 'updateHost', 'removeHost',
+ 'addLink', 'updateLink', 'removeLink'
+ ])).toBeTruthy();
+ });
+
+ // TODO: more tests...
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoInst-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoInst-spec.js
new file mode 100644
index 00000000..a6a12651
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoInst-spec.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Instance Service - Unit Tests
+ */
+describe('factory: view/topo/topoInst.js', function() {
+ var $log, fs, tis;
+
+ beforeEach(module('ovTopo', 'onosUtil', 'onosLayer'));
+
+ beforeEach(inject(function (_$log_, FnService, TopoInstService) {
+ $log = _$log_;
+ fs = FnService;
+ tis = TopoInstService;
+ }));
+
+ it('should define TopoInstService', function () {
+ expect(tis).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tis, [
+ 'initInst', 'destroyInst',
+ 'addInstance', 'updateInstance', 'removeInstance',
+ 'cancelAffinity',
+ 'isVisible', 'show', 'hide', 'toggle', 'showMaster'
+ ])).toBeTruthy();
+ });
+
+ // TODO: more tests...
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
new file mode 100644
index 00000000..725bd475
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Model Service - Unit Tests
+ */
+describe('factory: view/topo/topoModel.js', function() {
+ var $log, fs, rnd, tms;
+
+ // stop random numbers from being quite so random
+ var mockRandom = {
+ // mock spread returns s + 1
+ spread: function (s) {
+ return s + 1;
+ },
+ // mock random dimension returns d / 2 - 1
+ randDim: function (d) {
+ return d/2 - 1;
+ },
+ mock: 'yup'
+ };
+
+ // to mock out the [lng,lat] <=> [x,y] transformations, we will
+ // add/subtract 2000, 3000 respectively:
+ // lng:2005 === x:5, lat:3004 === y:4
+
+ var mockProjection = function (lnglat) {
+ return [lnglat[0] - 2000, lnglat[1] - 3000];
+ };
+
+ mockProjection.invert = function (xy) {
+ return [xy[0] + 2000, xy[1] + 3000];
+ };
+
+ // our test devices and hosts:
+ var dev1 = {
+ 'class': 'device',
+ id: 'dev1',
+ x: 17,
+ y: 27,
+ online: true
+ },
+ dev2 = {
+ 'class': 'device',
+ id: 'dev2',
+ x: 18,
+ y: 28,
+ online: true
+ },
+ host1 = {
+ 'class': 'host',
+ id: 'host1',
+ x: 23,
+ y: 33,
+ cp: {
+ device: 'dev1',
+ port: 7
+ },
+ ingress: 'dev1/7-host1'
+ },
+ host2 = {
+ 'class': 'host',
+ id: 'host2',
+ x: 24,
+ y: 34,
+ cp: {
+ device: 'dev0',
+ port: 0
+ },
+ ingress: 'dev0/0-host2'
+ };
+
+
+ // our test api
+ var api = {
+ projection: function () { return mockProjection; },
+ network: {
+ nodes: [dev1, dev2, host1, host2],
+ links: [],
+ lookup: {dev1: dev1, dev2: dev2, host1: host1, host2: host2},
+ revLinkToKey: {}
+ },
+ restyleLinkElement: function () {},
+ removeLinkElement: function () {}
+ };
+
+ // our test dimensions and well known locations..
+ var dim = [20, 40],
+ randLoc = [9, 19], // random location using randDim(): d/2-1
+ randHostLoc = [40, 50], // host "near" random location
+ // given that 'nearDist' = 15
+ // and spread(15) = 16
+ // 9 + 15 + 16 = 40; 19 + 15 + 16 = 50
+ nearDev1 = [48,58], // [17+15+16, 27+15+16]
+ dev1Loc = [17,27],
+ dev2Loc = [18,28],
+ host1Loc = [23,33],
+ host2Loc = [24,34];
+
+ // implement some custom matchers...
+ beforeEach(function () {
+ jasmine.addMatchers({
+ toBePositionedAt: function () {
+ return {
+ compare: function (actual, xy) {
+ var result = {},
+ actCoord = [actual.x, actual.y];
+
+ result.pass = (actual.x === xy[0]) && (actual.y === xy[1]);
+
+ if (result.pass) {
+ // for negation with ".not"
+ result.message = 'Expected [' + actCoord +
+ '] NOT to be positioned at [' + xy + ']';
+ } else {
+ result.message = 'Expected [' + actCoord +
+ '] to be positioned at [' + xy + ']';
+ }
+ return result;
+ }
+ }
+ },
+ toHaveEndPoints: function () {
+ return {
+ compare: function (actual, xy1, xy2) {
+ var result = {};
+
+ result.pass = (actual.source.x === xy1[0]) &&
+ (actual.source.y === xy1[1]) &&
+ (actual.target.x === xy2[0]) &&
+ (actual.target.y === xy2[1]);
+
+ if (result.pass) {
+ // for negation with ".not"
+ result.message = 'Expected ' + actual +
+ ' NOT to have endpoints [' + xy1 + ']-[' + xy2 + ']';
+ } else {
+ result.message = 'Expected ' + actual +
+ ' to have endpoints [' + xy1 + ']-[' + xy2 + ']';
+ }
+ return result;
+ }
+ }
+ },
+ toBeFixed: function () {
+ return {
+ compare: function (actual) {
+ var result = {
+ pass: actual.fixed
+ };
+ if (result.pass) {
+ result.message = 'Expected ' + actual +
+ ' NOT to be fixed!';
+ } else {
+ result.message = 'Expected ' + actual +
+ ' to be fixed!';
+ }
+ return result;
+ }
+ }
+ }
+ });
+ });
+
+ beforeEach(module('ovTopo', 'onosUtil'));
+
+ beforeEach(function () {
+ module(function ($provide) {
+ $provide.value('RandomService', mockRandom);
+ });
+ });
+
+ beforeEach(inject(function (_$log_, FnService, RandomService, TopoModelService) {
+ $log = _$log_;
+ fs = FnService;
+ rnd = RandomService;
+ tms = TopoModelService;
+ tms.initModel(api, dim);
+ }));
+
+
+ it('should install the mock random service', function () {
+ expect(rnd.mock).toBe('yup');
+ expect(rnd.spread(4)).toBe(5);
+ expect(rnd.randDim(8)).toBe(3);
+ });
+
+ it('should install the mock projection', function () {
+ expect(tms.coordFromLngLat({lng: 2005, lat: 3004})).toEqual([5,4]);
+ expect(tms.lngLatFromCoord([5,4])).toEqual([2005,3004]);
+ });
+
+ it('should define TopoModelService', function () {
+ expect(tms).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tms, [
+ 'initModel', 'newDim', 'destroyModel',
+ 'positionNode', 'createDeviceNode', 'createHostNode',
+ 'createHostLink', 'createLink',
+ 'coordFromLngLat', 'lngLatFromCoord',
+ 'findLink', 'findLinkById', 'findDevices',
+ 'findAttachedHosts', 'findAttachedLinks', 'findBadLinks'
+ ])).toBeTruthy();
+ });
+
+ // === unit tests for positionNode()
+
+ it('should position a node using meta x/y', function () {
+ var node = {
+ metaUi: { x:37, y:48 }
+ };
+ tms.positionNode(node);
+ expect(node).toBePositionedAt([37,48]);
+ expect(node).toBeFixed();
+ });
+
+ it('should position a node by translating lng/lat', function () {
+ var node = {
+ location: {
+ type: 'latlng',
+ lng: 2008,
+ lat: 3009
+ }
+ };
+ tms.positionNode(node);
+ expect(node).toBePositionedAt([8,9]);
+ expect(node).toBeFixed();
+ });
+
+ it('should position a device with no location randomly', function () {
+ var node = { 'class': 'device' };
+ tms.positionNode(node);
+ expect(node).toBePositionedAt(randLoc);
+ expect(node).not.toBeFixed();
+ });
+
+ it('should position a device randomly even if x/y set', function () {
+ var node = { 'class': 'device', x: 1, y: 2 };
+ tms.positionNode(node);
+ expect(node).toBePositionedAt(randLoc);
+ expect(node).not.toBeFixed();
+ });
+
+ it('should NOT reposition a device randomly on update', function () {
+ var node = { 'class': 'device', x: 1, y: 2 };
+ tms.positionNode(node, true);
+ expect(node).toBePositionedAt([1,2]);
+ expect(node).not.toBeFixed();
+ });
+
+ it('should position a host close to its device', function () {
+ var node = { 'class': 'host', cp: { device: 'dev1' } };
+ tms.positionNode(node);
+
+ // note: nearDist is 15; spread(15) adds 16; dev1 at [17,27]
+
+ expect(node).toBePositionedAt(nearDev1);
+ expect(node).not.toBeFixed();
+ });
+
+ it('should randomize host with no assoc device', function () {
+ var node = { 'class': 'host', cp: { device: 'dev0' } };
+ tms.positionNode(node);
+
+ // note: no device gives 'rand loc' [9,19]
+ // nearDist is 15; spread(15) adds 16
+
+ expect(node).toBePositionedAt(randHostLoc);
+ expect(node).not.toBeFixed();
+ });
+
+ // === unit tests for createDeviceNode()
+
+ it('should create a basic device node', function () {
+ var node = tms.createDeviceNode({ id: 'foo' });
+ expect(node).toBePositionedAt(randLoc);
+ expect(node).not.toBeFixed();
+ expect(node.class).toEqual('device');
+ expect(node.svgClass).toEqual('node device');
+ expect(node.id).toEqual('foo');
+ });
+
+ it('should create device node with type', function () {
+ var node = tms.createDeviceNode({ id: 'foo', type: 'cool' });
+ expect(node).toBePositionedAt(randLoc);
+ expect(node).not.toBeFixed();
+ expect(node.class).toEqual('device');
+ expect(node.svgClass).toEqual('node device cool');
+ expect(node.id).toEqual('foo');
+ });
+
+ it('should create online device node with type', function () {
+ var node = tms.createDeviceNode({ id: 'foo', type: 'cool', online: true });
+ expect(node).toBePositionedAt(randLoc);
+ expect(node).not.toBeFixed();
+ expect(node.class).toEqual('device');
+ expect(node.svgClass).toEqual('node device cool online');
+ expect(node.id).toEqual('foo');
+ });
+
+ it('should create online device node with type and lng/lat', function () {
+ var node = tms.createDeviceNode({
+ id: 'foo',
+ type: 'yowser',
+ online: true,
+ location: {
+ type: 'latlng',
+ lng: 2048,
+ lat: 3096
+ }
+ });
+ expect(node).toBePositionedAt([48,96]);
+ expect(node).toBeFixed();
+ expect(node.class).toEqual('device');
+ expect(node.svgClass).toEqual('node device yowser online');
+ expect(node.id).toEqual('foo');
+ });
+
+ // === unit tests for createHostNode()
+
+ it('should create a basic host node', function () {
+ var node = tms.createHostNode({ id: 'bar', cp: { device: 'dev0' } });
+ expect(node).toBePositionedAt(randHostLoc);
+ expect(node).not.toBeFixed();
+ expect(node.class).toEqual('host');
+ expect(node.svgClass).toEqual('node host endstation');
+ expect(node.id).toEqual('bar');
+ });
+
+ it('should create a host with type', function () {
+ var node = tms.createHostNode({
+ id: 'bar',
+ type: 'classic',
+ cp: { device: 'dev1' }
+ });
+ expect(node).toBePositionedAt(nearDev1);
+ expect(node).not.toBeFixed();
+ expect(node.class).toEqual('host');
+ expect(node.svgClass).toEqual('node host classic');
+ expect(node.id).toEqual('bar');
+ });
+
+ // === unit tests for createHostLink()
+
+ it('should create a basic host link', function () {
+ var link = tms.createHostLink(host1);
+ expect(link.source).toEqual(host1);
+ expect(link.target).toEqual(dev1);
+ expect(link).toHaveEndPoints(host1Loc, dev1Loc);
+ expect(link.key).toEqual('dev1/7-host1');
+ expect(link.class).toEqual('link');
+ expect(link.type()).toEqual('hostLink');
+ expect(link.linkWidth()).toEqual(1);
+ expect(link.online()).toEqual(true);
+ });
+
+ it('should return null for failed endpoint lookup', function () {
+ spyOn($log, 'error');
+ var link = tms.createHostLink(host2);
+ expect(link).toBeNull();
+ expect($log.error).toHaveBeenCalledWith(
+ 'Node(s) not on map for link:\n[dst] "dev0" missing'
+ );
+ });
+
+ // === unit tests for createLink()
+
+ it('should return null for missing endpoints', function () {
+ spyOn($log, 'error');
+ var link = tms.createLink({src: 'dev0', dst: 'dev00'});
+ expect(link).toBeNull();
+ expect($log.error).toHaveBeenCalledWith(
+ 'Node(s) not on map for link:\n[src] "dev0" missing\n[dst] "dev00" missing'
+ );
+ });
+
+ it('should create a basic link', function () {
+ var linkData = {
+ src: 'dev1',
+ dst: 'dev2',
+ id: 'baz',
+ type: 'zoo',
+ online: true,
+ linkWidth: 1.5
+ },
+ link = tms.createLink(linkData);
+ expect(link.source).toEqual(dev1);
+ expect(link.target).toEqual(dev2);
+ expect(link).toHaveEndPoints(dev1Loc, dev2Loc);
+ expect(link.key).toEqual('baz');
+ expect(link.class).toEqual('link');
+ expect(link.fromSource).toBe(linkData);
+ expect(link.type()).toEqual('zoo');
+ expect(link.online()).toEqual(true);
+ expect(link.linkWidth()).toEqual(1.5);
+ });
+
+ // TODO: more unit tests for additional functions....
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js
new file mode 100644
index 00000000..2b563295
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Oblique View Service - Unit Tests
+ */
+describe('factory: view/topo/topoOblique.js', function() {
+ var $log, fs, tos, flash;
+
+ beforeEach(module('ovTopo', 'onosUtil', 'onosLayer'));
+
+ beforeEach(inject(function (_$log_, FnService,
+ TopoObliqueService, FlashService) {
+ $log = _$log_;
+ fs = FnService;
+ tos = TopoObliqueService;
+ flash = FlashService;
+ }));
+
+ it('should define TopoTrafficService', function () {
+ expect(tos).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tos, [
+ 'initOblique', 'destroyOblique', 'isOblique', 'toggleOblique'
+ ])).toBeTruthy();
+ });
+
+ // TODO: more tests...
+});
+
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js
new file mode 100644
index 00000000..21513d1b
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Panel Service - Unit Tests
+ */
+describe('factory: view/topo/topoPanel.js', function() {
+ 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, 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();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tps, [
+ 'initPanels',
+ 'destroyPanels',
+ 'createTopoPanel',
+
+ 'showSummary',
+ 'toggleSummary',
+
+ 'toggleUseDetailsFlag',
+ 'displaySingle',
+ 'displayMulti',
+ '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...
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js
new file mode 100644
index 00000000..c8c051c9
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Selection Service - Unit Tests
+ */
+describe('factory: view/topo/topoSelect.js', function() {
+ var $log, fs, tss, bns;
+
+ beforeEach(module('ovTopo', 'onosUtil', 'onosLayer', 'ngRoute', 'onosNav',
+ 'onosWidget'));
+
+ beforeEach(inject(function (_$log_, FnService,
+ TopoSelectService, ButtonService) {
+ $log = _$log_;
+ fs = FnService;
+ tss = TopoSelectService;
+ bns = ButtonService;
+ }));
+
+ it('should define TopoSelectService', function () {
+ expect(tss).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tss, [
+ 'initSelect', 'destroySelect',
+ 'showDetails',
+ 'nodeMouseOver', 'nodeMouseOut', 'selectObject', 'deselectObject',
+ 'deselectAll',
+ 'hovered', 'selectOrder',
+ 'validateSelectionContext',
+ 'clickConsumed'
+ ])).toBeTruthy();
+ });
+
+ // TODO: more tests...
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js
new file mode 100644
index 00000000..eedc476f
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Toolbar Service - Unit Tests
+ */
+describe('factory: view/topo/topoToolbar.js', function() {
+ var $log, fs, ttbs, prefs, ps,
+ d3Elem;
+
+ beforeEach(module('ovTopo', 'onosUtil', 'onosLayer', 'ngRoute', 'onosNav',
+ 'onosWidget'));
+
+ beforeEach(inject(function (_$log_, FnService,
+ TopoToolbarService, PanelService, PrefsService) {
+ $log = _$log_;
+ fs = FnService;
+ ttbs = TopoToolbarService;
+ prefs = PrefsService;
+ ps = PanelService;
+ d3Elem = d3.select('body').append('div').attr('id', 'floatpanels');
+ ps.init();
+ }));
+
+ it('should define TopoToolbarService', function () {
+ expect(ttbs).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(ttbs, [
+ 'init', 'createToolbar', 'destroyToolbar',
+ 'keyListener', 'toggleToolbar'
+ ])).toBeTruthy();
+ });
+
+ // NOTE: topoToolbar relies too much on topo's closure variables
+ // to adequately test it
+
+}); \ No newline at end of file
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js
new file mode 100644
index 00000000..a04c2021
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2015 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 -- Topo View -- Topo Traffic Service - Unit Tests
+ */
+describe('factory: view/topo/topoTraffic.js', function() {
+ var $log, fs, tts;
+
+ beforeEach(module('ovTopo', 'onosUtil', 'onosLayer', 'onosNav', 'ngRoute'));
+
+ beforeEach(inject(function (_$log_, FnService, TopoTrafficService) {
+ $log = _$log_;
+ fs = FnService;
+ tts = TopoTrafficService;
+ }));
+
+ it('should define TopoTrafficService', function () {
+ expect(tts).toBeDefined();
+ });
+
+ it('should define api functions', function () {
+ expect(fs.areFunctions(tts, [
+ 'initTraffic', 'destroyTraffic', 'showTraffic',
+ 'cancelTraffic', 'requestTrafficForMode',
+ 'showRelatedIntentsAction', 'addHostIntentAction',
+ 'addMultiSourceIntentAction', 'showDeviceLinkFlowsAction',
+ 'showNextIntentAction', 'showPrevIntentAction',
+ 'showSelectedIntentTrafficAction', 'showAllTrafficAction'
+ ])).toBeTruthy();
+ });
+
+ // TODO: more tests...
+});
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/e2e/README.txt b/framework/src/onos/web/gui/src/main/webapp/tests/e2e/README.txt
new file mode 100644
index 00000000..4a23fa14
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/e2e/README.txt
@@ -0,0 +1,2 @@
+# End-to-End Tests (i.e. Scenario Tests)
+
diff --git a/framework/src/onos/web/gui/src/main/webapp/tests/karma.conf.js b/framework/src/onos/web/gui/src/main/webapp/tests/karma.conf.js
new file mode 100644
index 00000000..bd572c1b
--- /dev/null
+++ b/framework/src/onos/web/gui/src/main/webapp/tests/karma.conf.js
@@ -0,0 +1,90 @@
+// Karma configuration
+
+module.exports = function(config) {
+ config.set({
+
+ // base path that will be used to resolve all patterns (eg. files, exclude)
+ // the path is relative to this (karma.conf.js) file
+ basePath: '',
+
+
+ // frameworks to use
+ // available frameworks: https://npmjs.org/browse/keyword/karma-adapter
+ frameworks: ['jasmine'],
+
+
+ // list of files / patterns to load in the browser
+ files: [
+ // library code...
+ '../tp/angular.js',
+ '../tp/angular-mocks.js',
+ '../tp/angular-route.js',
+ '../tp/angular-cookies.js',
+ '../tp/d3.js',
+ '../tp/topojson.v1.min.js',
+
+ // production code...
+ // make sure modules are defined first...
+ '../onos.js',
+
+ '../app/fw/util/util.js',
+ '../app/fw/svg/svg.js',
+ '../app/fw/remote/remote.js',
+ '../app/fw/widget/widget.js',
+ '../app/fw/layer/layer.js',
+
+ '../app/view/topo/topo.js',
+
+ // now load services etc. that augment the modules
+ '../app/**/*.js',
+
+ // unit test code...
+ 'app/*-spec.js',
+ 'app/**/*-spec.js'
+ ],
+
+
+ // list of files to exclude
+ exclude: [
+ ],
+
+
+ // preprocess matching files before serving them to the browser
+ // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
+ preprocessors: {
+ },
+
+
+ // test results reporter to use
+ // possible values: 'dots', 'progress'
+ // available reporters: https://npmjs.org/browse/keyword/karma-reporter
+ reporters: ['progress'],
+
+
+ // web server port
+ port: 9876,
+
+
+ // enable / disable colors in the output (reporters and logs)
+ colors: true,
+
+
+ // level of logging
+ // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
+ logLevel: config.LOG_INFO,
+
+
+ // enable / disable watching file and executing tests whenever any file changes
+ autoWatch: true,
+
+
+ // start these browsers
+ // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
+ browsers: ['Chrome'],
+
+
+ // Continuous Integration mode
+ // if true, Karma captures browsers, runs the tests and exits
+ singleRun: false
+ });
+};