From 13d05bc8458758ee39cb829098241e89616717ee Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Wed, 9 Sep 2015 22:15:21 -0700 Subject: ONOS checkin based on commit tag e796610b1f721d02f9b0e213cf6f7790c10ecd60 Change-Id: Ife8810491034fe7becdba75dda20de4267bd15cd --- .../onos/web/gui/src/main/webapp/tests/README.txt | 31 ++ .../main/webapp/tests/app/fw/layer/flash-spec.js | 72 ++++ .../main/webapp/tests/app/fw/layer/panel-spec.js | 189 +++++++++ .../webapp/tests/app/fw/layer/quickhelp-spec.js | 214 ++++++++++ .../main/webapp/tests/app/fw/layer/veil-spec.js | 45 +++ .../src/main/webapp/tests/app/fw/mast/mast-spec.js | 37 ++ .../src/main/webapp/tests/app/fw/nav/nav-spec.js | 165 ++++++++ .../main/webapp/tests/app/fw/remote/rest-spec.js | 97 +++++ .../main/webapp/tests/app/fw/remote/urlfn-spec.js | 89 ++++ .../webapp/tests/app/fw/remote/websocket-spec.js | 268 +++++++++++++ .../webapp/tests/app/fw/remote/wsevent-spec.js | 78 ++++ .../main/webapp/tests/app/fw/svg/geodata-spec.js | 159 ++++++++ .../src/main/webapp/tests/app/fw/svg/glyph-spec.js | 425 ++++++++++++++++++++ .../src/main/webapp/tests/app/fw/svg/icon-spec.js | 106 +++++ .../src/main/webapp/tests/app/fw/svg/map-spec.js | 87 ++++ .../main/webapp/tests/app/fw/svg/svgUtil-spec.js | 237 +++++++++++ .../src/main/webapp/tests/app/fw/svg/zoom-spec.js | 152 +++++++ .../src/main/webapp/tests/app/fw/util/fn-spec.js | 446 +++++++++++++++++++++ .../src/main/webapp/tests/app/fw/util/keys-spec.js | 278 +++++++++++++ .../main/webapp/tests/app/fw/util/prefs-spec.js | 60 +++ .../main/webapp/tests/app/fw/util/random-spec.js | 110 +++++ .../main/webapp/tests/app/fw/util/theme-spec.js | 162 ++++++++ .../main/webapp/tests/app/fw/widget/button-spec.js | 300 ++++++++++++++ .../main/webapp/tests/app/fw/widget/table-spec.js | 340 ++++++++++++++++ .../tests/app/fw/widget/tableBuilder-spec.js | 95 +++++ .../webapp/tests/app/fw/widget/toolbar-spec.js | 180 +++++++++ .../webapp/tests/app/fw/widget/tooltip-spec.js | 79 ++++ .../web/gui/src/main/webapp/tests/app/onos-spec.js | 35 ++ .../webapp/tests/app/view/device/device-spec.js | 38 ++ .../webapp/tests/app/view/device/fakeData.json | 88 ++++ .../webapp/tests/app/view/topo/topoEvent-spec.js | 45 +++ .../webapp/tests/app/view/topo/topoFilter-spec.js | 70 ++++ .../webapp/tests/app/view/topo/topoForce-spec.js | 53 +++ .../webapp/tests/app/view/topo/topoInst-spec.js | 45 +++ .../webapp/tests/app/view/topo/topoModel-spec.js | 414 +++++++++++++++++++ .../webapp/tests/app/view/topo/topoOblique-spec.js | 45 +++ .../webapp/tests/app/view/topo/topoPanel-spec.js | 159 ++++++++ .../webapp/tests/app/view/topo/topoSelect-spec.js | 51 +++ .../webapp/tests/app/view/topo/topoToolbar-spec.js | 52 +++ .../webapp/tests/app/view/topo/topoTraffic-spec.js | 47 +++ .../web/gui/src/main/webapp/tests/e2e/README.txt | 2 + .../web/gui/src/main/webapp/tests/karma.conf.js | 90 +++++ 42 files changed, 5735 insertions(+) create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/README.txt create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/flash-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/panel-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/quickhelp-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/layer/veil-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/mast/mast-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/nav/nav-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/rest-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/websocket-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/remote/wsevent-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/geodata-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/glyph-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/icon-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/map-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/svgUtil-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/svg/zoom-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/fn-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/keys-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/prefs-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/random-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/util/theme-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/button-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/table-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tableBuilder-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/toolbar-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/fw/widget/tooltip-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/onos-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/device-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/device/fakeData.json create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoEvent-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoFilter-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoForce-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoInst-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoOblique-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoPanel-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoToolbar-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/e2e/README.txt create mode 100644 framework/src/onos/web/gui/src/main/webapp/tests/karma.conf.js (limited to 'framework/src/onos/web/gui/src/main/webapp/tests') 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 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 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 = + '
' + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
Host ID MAC Address Location
' + + '
' + + + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
Some IconSome IDSome MAC AddressSome Location
' + + '
' + + '
', + + onosSortableHeaderTags = + '
' + + '' + + '' + + '' + + '' + + '' + + '' + + '' + + '
Host ID MAC Address Location
' + + '
'; + + 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( + '' + + '' + ); + h[1].click(); + div = currH.find('div'); + expect(div.html()).toBe( + '' + + '' + ); + + 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( + '' + + '' + ); + } + + 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 + }); +}; -- cgit 1.2.3-korg