GUI -- TopoView - Reimplemented 'traffic' related functionality.

Change-Id: I86d16324e4ce2cd2e0eb8d8f48f72804d7ce101f
diff --git a/web/gui/src/main/webapp/app/index.html b/web/gui/src/main/webapp/app/index.html
index ccc737b..3b05d23 100644
--- a/web/gui/src/main/webapp/app/index.html
+++ b/web/gui/src/main/webapp/app/index.html
@@ -86,6 +86,7 @@
     <script src="view/topo/topoModel.js"></script>
     <script src="view/topo/topoPanel.js"></script>
     <script src="view/topo/topoSelect.js"></script>
+    <script src="view/topo/topoTraffic.js"></script>
     <script src="view/device/device.js"></script>
     <!-- TODO: inject javascript refs server-side -->
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topo.js b/web/gui/src/main/webapp/app/view/topo/topo.js
index 6e3dd5f..24fb013 100644
--- a/web/gui/src/main/webapp/app/view/topo/topo.js
+++ b/web/gui/src/main/webapp/app/view/topo/topo.js
@@ -28,7 +28,7 @@
     ];
 
     // references to injected services etc.
-    var $log, fs, ks, zs, gs, ms, sus, tes, tfs, tps, tis, tss;
+    var $log, fs, ks, zs, gs, ms, sus, tes, tfs, tps, tis, tss, tts;
 
     // DOM elements
     var ovtopo, svg, defs, zoomLayer, mapG, forceG, noDevsLayer;
@@ -57,12 +57,12 @@
             U: [tfs.unpin, 'Unpin node (hover mouse over)'],
             R: [resetZoom, 'Reset pan / zoom'],
 
-            //V: [showRelatedIntentsAction, 'Show all related intents'],
-            //rightArrow: [showNextIntentAction, 'Show next related intent'],
-            //leftArrow: [showPrevIntentAction, 'Show previous related intent'],
-            //W: [showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
-            //A: [showAllTrafficAction, 'Monitor all traffic'],
-            //F: [showDeviceLinkFlowsAction, 'Show device link flows'],
+            V: [tts.showRelatedIntentsAction, 'Show all related intents'],
+            rightArrow: [tts.showNextIntentAction, 'Show next related intent'],
+            leftArrow: [tts.showPrevIntentAction, 'Show previous related intent'],
+            W: [tts.showSelectedIntentTrafficAction, 'Monitor traffic of selected intent'],
+            A: [tts.showAllTrafficAction, 'Monitor all traffic'],
+            F: [tts.showDeviceLinkFlowsAction, 'Show device link flows'],
 
             //E: [equalizeMasters, 'Equalize mastership roles'],
 
@@ -222,11 +222,11 @@
             'FnService', 'MastService', 'KeyService', 'ZoomService',
             'GlyphService', 'MapService', 'SvgUtilService',
             'TopoEventService', 'TopoForceService', 'TopoPanelService',
-            'TopoInstService', 'TopoSelectService',
+            'TopoInstService', 'TopoSelectService', 'TopoTrafficService',
 
         function ($scope, _$log_, $loc, $timeout, _fs_, mast,
                   _ks_, _zs_, _gs_, _ms_, _sus_,
-                  _tes_, _tfs_, _tps_, _tis_, _tss_) {
+                  _tes_, _tfs_, _tps_, _tis_, _tss_, _tts_) {
             var self = this,
                 projection,
                 dim,
@@ -249,6 +249,7 @@
             tps = _tps_;
             tis = _tis_;
             tss = _tss_;
+            tts = _tts_;
 
             self.notifyResize = function () {
                 svgResized(fs.windowSize(mast.mastHeight()));
diff --git a/web/gui/src/main/webapp/app/view/topo/topoEvent.js b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
index 10ed6df..f04c654 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoEvent.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoEvent.js
@@ -27,7 +27,7 @@
     'use strict';
 
     // injected refs
-    var $log, wss, wes, tps, tis, tfs, tss;
+    var $log, wss, wes, tps, tis, tfs, tss, tts;
 
     // internal state
     var wsock, evApis;
@@ -40,6 +40,8 @@
 
             showDetails: tss,
 
+            showTraffic: tts,
+
             addInstance: tis,
             updateInstance: tis,
             removeInstance: tis,
@@ -53,8 +55,6 @@
             addLink: tfs,
             updateLink: tfs,
             removeLink: tfs
-
-            // TODO: add remaining event api vectors
         };
     }
 
@@ -106,9 +106,10 @@
     .factory('TopoEventService',
         ['$log', '$location', 'WebSocketService', 'WsEventService',
             'TopoPanelService', 'TopoInstService', 'TopoForceService',
-            'TopoSelectService',
+            'TopoSelectService', 'TopoTrafficService',
 
-        function (_$log_, $loc, _wss_, _wes_, _tps_, _tis_, _tfs_, _tss_) {
+        function (_$log_, $loc, _wss_, _wes_,
+                  _tps_, _tis_, _tfs_, _tss_, _tts_) {
             $log = _$log_;
             wss = _wss_;
             wes = _wes_;
@@ -116,6 +117,7 @@
             tis = _tis_;
             tfs = _tfs_;
             tss = _tss_;
+            tts = _tts_;
 
             bindApis();
 
diff --git a/web/gui/src/main/webapp/app/view/topo/topoForce.js b/web/gui/src/main/webapp/app/view/topo/topoForce.js
index 87b554e..35c606f 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoForce.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoForce.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // injected refs
-    var $log, fs, sus, is, ts, flash, tis, tms, tss, icfg, uplink;
+    var $log, fs, sus, is, ts, flash, tis, tms, tss, tts, icfg, uplink;
 
     // configuration
     var labelConfig = {
@@ -996,6 +996,21 @@
         return true;
     }
 
+    // ==========================
+    // function entry points for traffic module
+
+    var allTrafficClasses = 'primary secondary animated optical';
+
+    function clearLinkTrafficStyle() {
+        link.style('stroke-width', null)
+            .classed(allTrafficClasses, false);
+    }
+
+    function removeLinkLabels() {
+        network.links.forEach(function (d) {
+            d.label = '';
+        });
+    }
 
     // ==========================
     // Module definition
@@ -1018,13 +1033,27 @@
         };
     }
 
+    function mkTrafficApi(uplink) {
+        return {
+            clearLinkTrafficStyle: clearLinkTrafficStyle,
+            removeLinkLabels: removeLinkLabels,
+            updateLinks: updateLinks,
+            findLinkById: tms.findLinkById,
+            hovered: tss.hovered,
+            validateSelectionContext: tss.validateSelectionContext,
+            selectOrder: tss.selectOrder,
+            sendEvent: uplink.sendEvent
+        }
+    }
+
     angular.module('ovTopo')
     .factory('TopoForceService',
         ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService',
             'FlashService', 'TopoInstService', 'TopoModelService',
-            'TopoSelectService',
+            'TopoSelectService', 'TopoTrafficService',
 
-        function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_, _tms_, _tss_) {
+        function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_,
+                  _tis_, _tms_, _tss_, _tts_) {
             $log = _$log_;
             fs = _fs_;
             sus = _sus_;
@@ -1034,6 +1063,7 @@
             tis = _tis_;
             tms = _tms_;
             tss = _tss_;
+            tts = _tts_;
 
             icfg = is.iconConfig();
 
@@ -1049,6 +1079,7 @@
 
                 tms.initModel(mkModelApi(uplink), dim);
                 tss.initSelect(mkSelectApi(uplink));
+                tts.initTraffic(mkTrafficApi(uplink));
 
                 settings = angular.extend({}, defaultSettings, opts);
 
@@ -1083,7 +1114,9 @@
             }
 
             function destroyForce() {
-
+                tts.destroyTraffic();
+                tss.destroySelect();
+                tms.destroyModel();
             }
 
             return {
diff --git a/web/gui/src/main/webapp/app/view/topo/topoModel.js b/web/gui/src/main/webapp/app/view/topo/topoModel.js
index de31eef..b1f02ae 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoModel.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoModel.js
@@ -376,9 +376,12 @@
                 dim = _dim_;
             }
 
+            function destroyModel() { }
+
             return {
                 initModel: initModel,
                 newDim: newDim,
+                destroyModel: destroyModel,
 
                 positionNode: positionNode,
                 createDeviceNode: createDeviceNode,
diff --git a/web/gui/src/main/webapp/app/view/topo/topoSelect.js b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
index f431d9e..0513477 100644
--- a/web/gui/src/main/webapp/app/view/topo/topoSelect.js
+++ b/web/gui/src/main/webapp/app/view/topo/topoSelect.js
@@ -23,7 +23,7 @@
     'use strict';
 
     // injected refs
-    var $log, fs, flash, tps;
+    var $log, fs, flash, tps, tts;
 
     // api to topoForce
     var api;
@@ -65,7 +65,7 @@
             $log.debug("MouseOver()...", m);
             if (hovered != m) {
                 hovered = m;
-                requestTrafficForMode();
+                tts.requestTrafficForMode();
             }
         }
     }
@@ -74,7 +74,7 @@
         if (!m.dragStarted) {
             if (hovered) {
                 hovered = null;
-                requestTrafficForMode();
+                tts.requestTrafficForMode();
             }
             $log.debug("MouseOut()...", m);
         }
@@ -118,8 +118,6 @@
         n.classed('selected', true);
         api.updateDeviceColors(obj);
         updateDetail();
-
-        debugSel();
     }
 
     function deselectObject(id) {
@@ -130,8 +128,6 @@
             fs.removeFromArray(id, selectOrder);
             api.updateDeviceColors(obj.obj);
         }
-
-        debugSel();
     }
 
     function deselectAll() {
@@ -141,12 +137,6 @@
         selectOrder = [];
         api.updateDeviceColors();
         updateDetail();
-
-        debugSel();
-    }
-
-    function debugSel() {
-        $log.debug(' ..... Selected now >> ', selectOrder);
     }
 
     // === -----------------------------------------------------
@@ -175,14 +165,14 @@
     function emptySelect() {
         haveDetails = false;
         tps.hideDetailPanel();
-        cancelTraffic();
+        tts.cancelTraffic();
     }
 
     function singleSelect() {
         // NOTE: detail is shown from 'showDetails' event callback
         requestDetails();
-        cancelTraffic();
-        requestTrafficForMode();
+        tts.cancelTraffic();
+        tts.requestTrafficForMode();
     }
 
     function multiSelect() {
@@ -192,17 +182,17 @@
         tps.displayMulti(selectOrder);
 
         // always add the 'show traffic' action
-        tps.addAction('Show Related Traffic', showRelatedIntentsAction);
+        tps.addAction('Show Related Traffic', tts.showRelatedIntentsAction);
 
         // add other actions, based on what is selected...
         if (nSel() === 2 && allSelectionsClass('host')) {
-            tps.addAction('Create Host-to-Host Flow', addHostIntentAction);
+            tps.addAction('Create Host-to-Host Flow', tts.addHostIntentAction);
         } else if (nSel() >= 2 && allSelectionsClass('host')) {
-            tps.addAction('Create Multi-Source Flow', addMultiSourceIntentAction);
+            tps.addAction('Create Multi-Source Flow', tts.addMultiSourceIntentAction);
         }
 
-        cancelTraffic();
-        requestTrafficForMode();
+        tts.cancelTraffic();
+        tts.requestTrafficForMode();
     }
 
 
@@ -216,11 +206,11 @@
         tps.displaySingle(data);
 
         // always add the 'show traffic' action
-        tps.addAction('Show Related Traffic', showRelatedIntentsAction);
+        tps.addAction('Show Related Traffic', tts.showRelatedIntentsAction);
 
         // add other actions, based on what is selected...
         if (data.type === 'switch') {
-            tps.addAction('Show Device Flows', showDeviceLinkFlowsAction);
+            tps.addAction('Show Device Flows', tts.showDeviceLinkFlowsAction);
         }
 
         // only show the details panel if the user hasn't "hidden" it
@@ -242,34 +232,13 @@
         }
     }
 
-    // === -----------------------------------------------------
-    //  TODO: migrate these to topoTraffic.js
-
-    function cancelTraffic() {
-        $log.debug('TODO: cancelTraffic');
-
+    function validateSelectionContext() {
+        if (!hovered && !nSel()) {
+            tts.cancelTraffic();
+            return false;
+        }
+        return true;
     }
-    function requestTrafficForMode() {
-        $log.debug('TODO: requestTrafficForMode');
-
-    }
-    function showRelatedIntentsAction () {
-        $log.debug('TODO: showRelatedIntentsAction');
-
-    }
-    function addHostIntentAction () {
-        $log.debug('TODO: addHostIntentAction');
-
-    }
-    function addMultiSourceIntentAction () {
-        $log.debug('TODO: addMultiSourceIntentAction');
-
-    }
-    function showDeviceLinkFlowsAction () {
-        $log.debug('TODO: showDeviceLinkFlowsAction');
-
-    }
-
 
     // === -----------------------------------------------------
     // === MODULE DEFINITION ===
@@ -277,12 +246,14 @@
     angular.module('ovTopo')
         .factory('TopoSelectService',
         ['$log', 'FnService', 'FlashService', 'TopoPanelService',
+            'TopoTrafficService',
 
-        function (_$log_, _fs_, _flash_, _tps_) {
+        function (_$log_, _fs_, _flash_, _tps_, _tts_) {
             $log = _$log_;
             fs = _fs_;
             flash = _flash_;
             tps = _tps_;
+            tts = _tts_;
 
             function initSelect(_api_) {
                 api = _api_;
@@ -302,8 +273,11 @@
                 selectObject: selectObject,
                 deselectObject: deselectObject,
                 deselectAll: deselectAll,
+
                 hovered: function () { return hovered; },
-                haveDetails: function () { return haveDetails; }
+                haveDetails: function () { return haveDetails; },
+                selectOrder: function () { return selectOrder; },
+                validateSelectionContext: validateSelectionContext
             };
         }]);
 }());
diff --git a/web/gui/src/main/webapp/app/view/topo/topoTraffic.js b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
new file mode 100644
index 0000000..b1563ab
--- /dev/null
+++ b/web/gui/src/main/webapp/app/view/topo/topoTraffic.js
@@ -0,0 +1,234 @@
+/*
+ * 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 -- Topology Traffic Module.
+ Defines behavior for viewing different traffic modes.
+ */
+
+(function () {
+    'use strict';
+
+    // injected refs
+    var $log, fs, flash;
+
+    // api to topoForce
+    var api;
+    /*
+     clearLinkTrafficStyle()
+     removeLinkLabels()
+     updateLinks()
+     findLinkById( id )
+     hovered()
+     validateSelectionContext()
+     sendEvent( type, {payload} )
+     */
+
+    // constants
+    var hoverModeNone = 0,
+        hoverModeAll = 1,
+        hoverModeFlows = 2,
+        hoverModeIntents = 3;
+
+    // internal state
+    var hoverMode = hoverModeNone;
+
+
+    // === -----------------------------------------------------
+    //  Event Handlers
+
+    function showTraffic(data) {
+        var paths = data.paths;
+
+        api.clearLinkTrafficStyle();
+        api.removeLinkLabels();
+
+        // Now highlight all links in the paths payload, and attach
+        //  labels to them, if they are defined.
+        paths.forEach(function (p) {
+            var n = p.links.length,
+                i, ldata;
+
+            for (i=0; i<n; i++) {
+                ldata = api.findLinkById(p.links[i]);
+                if (ldata && ldata.el) {
+                    ldata.el.classed(p.class, true);
+                    ldata.label = p.labels[i];
+                }
+            }
+        });
+
+        api.updateLinks();
+    }
+
+    // === -----------------------------------------------------
+    //  Helper functions
+
+    function requestDeviceLinkFlows() {
+        var hov = api.hovered();
+
+        function hoverValid() {
+            return hoverMode === hoverModeFlows &&
+                hov && (hov.class === 'device');
+        }
+
+        if (api.validateSelectionContext()) {
+            api.sendEvent('requestDeviceLinkFlows', {
+                ids: api.selectOrder(),
+                hover: hoverValid() ? hov.id : ''
+            });
+        }
+    }
+
+    function requestRelatedIntents() {
+        var hov = api.hovered();
+
+        function hoverValid() {
+            return hoverMode === hoverModeIntents &&
+                hov && (hov.class === 'host' || hov.class === 'device');
+        }
+
+        if (api.validateSelectionContext()) {
+            api.sendEvent('requestRelatedIntents', {
+                ids: api.selectOrder(),
+                hover: hoverValid() ? hov.id : ''
+            });
+        }
+    }
+
+
+    // === -----------------------------------------------------
+    //  Traffic requests
+
+    function cancelTraffic() {
+        api.sendEvent('cancelTraffic');
+    }
+
+    // invoked in response to change in selection and/or mouseover/out:
+    function requestTrafficForMode() {
+        if (hoverMode === hoverModeFlows) {
+            requestDeviceLinkFlows();
+        } else if (hoverMode === hoverModeIntents) {
+            requestRelatedIntents();
+        }
+    }
+
+    // === -----------------------------
+    // keystroke commands
+
+    // keystroke-right-arrow (see topo.js)
+    function showNextIntentAction() {
+        hoverMode = hoverModeNone;
+        api.sendEvent('requestNextRelatedIntent');
+        flash.flash('>');
+    }
+
+    // keystroke-left-arrow (see topo.js)
+    function showPrevIntentAction() {
+        hoverMode = hoverModeNone;
+        api.sendEvent('requestPrevRelatedIntent');
+        flash.flash('<');
+    }
+
+    // keystroke-W (see topo.js)
+    function showSelectedIntentTrafficAction() {
+        hoverMode = hoverModeNone;
+        api.sendEvent('requestSelectedIntentTraffic');
+        flash.flash('Traffic on Selected Path');
+    }
+
+    // keystroke-A (see topo.js)
+    function showAllTrafficAction() {
+        hoverMode = hoverModeAll;
+        api.sendEvent('requestAllTraffic');
+        flash.flash('All Traffic');
+    }
+
+    // === -----------------------------
+    // action buttons on detail panel
+
+    // also, keystroke-V (see topo.js)
+    function showRelatedIntentsAction () {
+        hoverMode = hoverModeIntents;
+        requestRelatedIntents();
+        flash.flash('Related Paths');
+    }
+
+    function addHostIntentAction () {
+        var so = api.selectOrder();
+        api.sendEvent('addHostIntent', {
+            one: so[0],
+            two: so[1],
+            ids: so
+        });
+        flash.flash('Host-to-Host flow added');
+    }
+
+    function addMultiSourceIntentAction () {
+        var so = api.selectOrder();
+        api.sendEvent('addMultiSourceIntent', {
+            src: so.slice(0, so.length - 1),
+            dst: so[so.length - 1],
+            ids: so
+        });
+        flash.flash('Multi-Source flow added');
+    }
+
+    // also, keystroke-F (see topo.js)
+    function showDeviceLinkFlowsAction () {
+        hoverMode = hoverModeFlows;
+        requestDeviceLinkFlows();
+        flash.flash('Device Flows');
+    }
+
+
+    // === -----------------------------------------------------
+    // === MODULE DEFINITION ===
+
+    angular.module('ovTopo')
+        .factory('TopoTrafficService',
+        ['$log', 'FnService', 'FlashService',
+
+        function (_$log_, _fs_, _flash_) {
+            $log = _$log_;
+            fs = _fs_;
+            flash = _flash_;
+
+            function initTraffic(_api_) {
+                api = _api_;
+            }
+
+            function destroyTraffic() { }
+
+            return {
+                initTraffic: initTraffic,
+                destroyTraffic: destroyTraffic,
+
+                showTraffic: showTraffic,
+
+                cancelTraffic: cancelTraffic,
+                requestTrafficForMode: requestTrafficForMode,
+                showRelatedIntentsAction: showRelatedIntentsAction,
+                addHostIntentAction: addHostIntentAction,
+                addMultiSourceIntentAction: addMultiSourceIntentAction,
+                showDeviceLinkFlowsAction: showDeviceLinkFlowsAction,
+                showNextIntentAction: showNextIntentAction,
+                showPrevIntentAction: showPrevIntentAction,
+                showSelectedIntentTrafficAction: showSelectedIntentTrafficAction,
+                showAllTrafficAction: showAllTrafficAction
+            };
+        }]);
+}());
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
index 67291f3..84c1f61 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoModel-spec.js
@@ -207,7 +207,7 @@
 
     it('should define api functions', function () {
         expect(fs.areFunctions(tms, [
-            'initModel', 'newDim',
+            'initModel', 'newDim', 'destroyModel',
             'positionNode', 'createDeviceNode', 'createHostNode',
             'createHostLink', 'createLink',
             'coordFromLngLat', 'lngLatFromCoord',
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js
index e640764..78bde80 100644
--- a/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js
+++ b/web/gui/src/main/webapp/tests/app/view/topo/topoSelect-spec.js
@@ -36,7 +36,8 @@
         expect(fs.areFunctions(tss, [
             'initSelect', 'destroySelect', 'showDetails', 'toggleDetails',
             'nodeMouseOver', 'nodeMouseOut', 'selectObject', 'deselectObject',
-            'deselectAll', 'hovered', 'haveDetails'
+            'deselectAll', 'hovered', 'haveDetails', 'selectOrder',
+            'validateSelectionContext'
         ])).toBeTruthy();
     });
 
diff --git a/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js b/web/gui/src/main/webapp/tests/app/view/topo/topoTraffic-spec.js
new file mode 100644
index 0000000..ecc4a34
--- /dev/null
+++ b/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'));
+
+    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...
+});