GUI -- TopoView - Migrated helper functions to topoModel.js.
- moved randomized functions to random.js (so we can mock them). Change-Id: Ic56ce64c036d36f34798f0df9f03a7d09335a2ab
Showing
9 changed files
with
872 additions
and
232 deletions
1 | +/* | ||
2 | + * Copyright 2014,2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Random -- Encapsulated randomness | ||
19 | + */ | ||
20 | +(function () { | ||
21 | + 'use strict'; | ||
22 | + | ||
23 | + var $log, fs; | ||
24 | + | ||
25 | + var halfRoot2 = 0.7071; | ||
26 | + | ||
27 | + // given some value, s, returns an integer between -s/2 and s/2 | ||
28 | + // e.g. s = 100; result in the range [-50..50) | ||
29 | + function spread(s) { | ||
30 | + return Math.floor((Math.random() * s) - s / 2); | ||
31 | + } | ||
32 | + | ||
33 | + // for a given dimension, d, choose a random value somewhere between | ||
34 | + // 0 and d where the value is within (d / (2 * sqrt(2))) of d/2. | ||
35 | + function randDim(d) { | ||
36 | + return d / 2 + spread(d * halfRoot2); | ||
37 | + } | ||
38 | + | ||
39 | + angular.module('onosUtil') | ||
40 | + .factory('RandomService', ['$log', 'FnService', | ||
41 | + | ||
42 | + function (_$log_, _fs_) { | ||
43 | + $log = _$log_; | ||
44 | + fs = _fs_; | ||
45 | + | ||
46 | + return { | ||
47 | + spread: spread, | ||
48 | + randDim: randDim | ||
49 | + }; | ||
50 | + }]); | ||
51 | +}()); |
... | @@ -35,6 +35,7 @@ | ... | @@ -35,6 +35,7 @@ |
35 | 35 | ||
36 | <script src="fw/util/util.js"></script> | 36 | <script src="fw/util/util.js"></script> |
37 | <script src="fw/util/fn.js"></script> | 37 | <script src="fw/util/fn.js"></script> |
38 | + <script src="fw/util/random.js"></script> | ||
38 | <script src="fw/util/theme.js"></script> | 39 | <script src="fw/util/theme.js"></script> |
39 | <script src="fw/util/keys.js"></script> | 40 | <script src="fw/util/keys.js"></script> |
40 | 41 | ||
... | @@ -81,6 +82,7 @@ | ... | @@ -81,6 +82,7 @@ |
81 | <script src="view/topo/topo.js"></script> | 82 | <script src="view/topo/topo.js"></script> |
82 | <script src="view/topo/topoEvent.js"></script> | 83 | <script src="view/topo/topoEvent.js"></script> |
83 | <script src="view/topo/topoForce.js"></script> | 84 | <script src="view/topo/topoForce.js"></script> |
85 | + <script src="view/topo/topoModel.js"></script> | ||
84 | <script src="view/topo/topoPanel.js"></script> | 86 | <script src="view/topo/topoPanel.js"></script> |
85 | <script src="view/topo/topoInst.js"></script> | 87 | <script src="view/topo/topoInst.js"></script> |
86 | <script src="view/device/device.js"></script> | 88 | <script src="view/device/device.js"></script> | ... | ... |
... | @@ -130,8 +130,8 @@ | ... | @@ -130,8 +130,8 @@ |
130 | 130 | ||
131 | 131 | ||
132 | // callback invoked when the SVG view has been resized.. | 132 | // callback invoked when the SVG view has been resized.. |
133 | - function svgResized(dim) { | 133 | + function svgResized(s) { |
134 | - tfs.resize(dim); | 134 | + tfs.newDim([s.width, s.height]); |
135 | } | 135 | } |
136 | 136 | ||
137 | // --- Background Map ------------------------------------------------ | 137 | // --- Background Map ------------------------------------------------ |
... | @@ -203,6 +203,7 @@ | ... | @@ -203,6 +203,7 @@ |
203 | _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) { | 203 | _ks_, _zs_, _gs_, _ms_, _sus_, tes, _tfs_, tps, _tis_) { |
204 | var self = this, | 204 | var self = this, |
205 | projection, | 205 | projection, |
206 | + dim, | ||
206 | uplink = { | 207 | uplink = { |
207 | // provides function calls back into this space | 208 | // provides function calls back into this space |
208 | showNoDevs: showNoDevs, | 209 | showNoDevs: showNoDevs, |
... | @@ -230,6 +231,7 @@ | ... | @@ -230,6 +231,7 @@ |
230 | tes.closeSock(); | 231 | tes.closeSock(); |
231 | tps.destroyPanels(); | 232 | tps.destroyPanels(); |
232 | tis.destroyInst(); | 233 | tis.destroyInst(); |
234 | + tfs.destroyForce(); | ||
233 | }); | 235 | }); |
234 | 236 | ||
235 | // svg layer and initialization of components | 237 | // svg layer and initialization of components |
... | @@ -237,6 +239,7 @@ | ... | @@ -237,6 +239,7 @@ |
237 | svg = ovtopo.select('svg'); | 239 | svg = ovtopo.select('svg'); |
238 | // set the svg size to match that of the window, less the masthead | 240 | // set the svg size to match that of the window, less the masthead |
239 | svg.attr(fs.windowSize(mast.mastHeight())); | 241 | svg.attr(fs.windowSize(mast.mastHeight())); |
242 | + dim = [svg.attr('width'), svg.attr('height')]; | ||
240 | 243 | ||
241 | setUpKeys(); | 244 | setUpKeys(); |
242 | setUpDefs(); | 245 | setUpDefs(); |
... | @@ -250,7 +253,7 @@ | ... | @@ -250,7 +253,7 @@ |
250 | ); | 253 | ); |
251 | 254 | ||
252 | forceG = zoomLayer.append('g').attr('id', 'topo-force'); | 255 | forceG = zoomLayer.append('g').attr('id', 'topo-force'); |
253 | - tfs.initForce(forceG, uplink, svg.attr('width'), svg.attr('height')); | 256 | + tfs.initForce(forceG, uplink, dim); |
254 | tis.initInst(); | 257 | tis.initInst(); |
255 | tps.initPanels(); | 258 | tps.initPanels(); |
256 | tes.openSock(); | 259 | tes.openSock(); | ... | ... |
... | @@ -15,15 +15,15 @@ | ... | @@ -15,15 +15,15 @@ |
15 | */ | 15 | */ |
16 | 16 | ||
17 | /* | 17 | /* |
18 | - ONOS GUI -- Topology Event Module. | 18 | + ONOS GUI -- Topology Force Module. |
19 | - Defines event handling for events received from the server. | 19 | + Visualization of the topology in an SVG layer, using a D3 Force Layout. |
20 | */ | 20 | */ |
21 | 21 | ||
22 | (function () { | 22 | (function () { |
23 | 'use strict'; | 23 | 'use strict'; |
24 | 24 | ||
25 | // injected refs | 25 | // injected refs |
26 | - var $log, fs, sus, is, ts, flash, tis, icfg, uplink; | 26 | + var $log, fs, sus, is, ts, flash, tis, tms, icfg, uplink; |
27 | 27 | ||
28 | // configuration | 28 | // configuration |
29 | var labelConfig = { | 29 | var labelConfig = { |
... | @@ -48,7 +48,7 @@ | ... | @@ -48,7 +48,7 @@ |
48 | light: { | 48 | light: { |
49 | baseColor: '#666', | 49 | baseColor: '#666', |
50 | inColor: '#66f', | 50 | inColor: '#66f', |
51 | - outColor: '#f00', | 51 | + outColor: '#f00' |
52 | }, | 52 | }, |
53 | dark: { | 53 | dark: { |
54 | baseColor: '#aaa', | 54 | baseColor: '#aaa', |
... | @@ -76,7 +76,7 @@ | ... | @@ -76,7 +76,7 @@ |
76 | showOffline = true, // whether offline devices are displayed | 76 | showOffline = true, // whether offline devices are displayed |
77 | oblique = false, // whether we are in the oblique view | 77 | oblique = false, // whether we are in the oblique view |
78 | nodeLock = false, // whether nodes can be dragged or not (locked) | 78 | nodeLock = false, // whether nodes can be dragged or not (locked) |
79 | - width, height, // the width and height of the force layout | 79 | + dim, // the dimensions of the force layout [w,h] |
80 | hovered, // the node over which the mouse is hovering | 80 | hovered, // the node over which the mouse is hovering |
81 | selections = {}, // what is currently selected | 81 | selections = {}, // what is currently selected |
82 | selectOrder = []; // the order in which we made selections | 82 | selectOrder = []; // the order in which we made selections |
... | @@ -131,7 +131,7 @@ | ... | @@ -131,7 +131,7 @@ |
131 | return; | 131 | return; |
132 | } | 132 | } |
133 | 133 | ||
134 | - d = createDeviceNode(data); | 134 | + d = tms.createDeviceNode(data); |
135 | network.nodes.push(d); | 135 | network.nodes.push(d); |
136 | lu[id] = d; | 136 | lu[id] = d; |
137 | 137 | ||
... | @@ -149,7 +149,7 @@ | ... | @@ -149,7 +149,7 @@ |
149 | if (d) { | 149 | if (d) { |
150 | wasOnline = d.online; | 150 | wasOnline = d.online; |
151 | angular.extend(d, data); | 151 | angular.extend(d, data); |
152 | - if (positionNode(d, true)) { | 152 | + if (tms.positionNode(d, true)) { |
153 | sendUpdateMeta(d); | 153 | sendUpdateMeta(d); |
154 | } | 154 | } |
155 | updateNodes(); | 155 | updateNodes(); |
... | @@ -185,7 +185,7 @@ | ... | @@ -185,7 +185,7 @@ |
185 | return; | 185 | return; |
186 | } | 186 | } |
187 | 187 | ||
188 | - d = createHostNode(data); | 188 | + d = tms.createHostNode(data); |
189 | network.nodes.push(d); | 189 | network.nodes.push(d); |
190 | lu[id] = d; | 190 | lu[id] = d; |
191 | 191 | ||
... | @@ -193,7 +193,7 @@ | ... | @@ -193,7 +193,7 @@ |
193 | 193 | ||
194 | updateNodes(); | 194 | updateNodes(); |
195 | 195 | ||
196 | - lnk = createHostLink(data); | 196 | + lnk = tms.createHostLink(data); |
197 | if (lnk) { | 197 | if (lnk) { |
198 | 198 | ||
199 | $log.debug("Created new host-link.. ", lnk.key); | 199 | $log.debug("Created new host-link.. ", lnk.key); |
... | @@ -213,7 +213,7 @@ | ... | @@ -213,7 +213,7 @@ |
213 | d = lu[id]; | 213 | d = lu[id]; |
214 | if (d) { | 214 | if (d) { |
215 | angular.extend(d, data); | 215 | angular.extend(d, data); |
216 | - if (positionNode(d, true)) { | 216 | + if (tms.positionNode(d, true)) { |
217 | sendUpdateMeta(d); | 217 | sendUpdateMeta(d); |
218 | } | 218 | } |
219 | updateNodes(); | 219 | updateNodes(); |
... | @@ -251,7 +251,7 @@ | ... | @@ -251,7 +251,7 @@ |
251 | } | 251 | } |
252 | 252 | ||
253 | // no backing store link yet | 253 | // no backing store link yet |
254 | - d = createLink(data); | 254 | + d = tms.createLink(data); |
255 | if (d) { | 255 | if (d) { |
256 | network.links.push(d); | 256 | network.links.push(d); |
257 | lu[d.key] = d; | 257 | lu[d.key] = d; |
... | @@ -290,42 +290,6 @@ | ... | @@ -290,42 +290,6 @@ |
290 | restyleLinkElement(ldata); | 290 | restyleLinkElement(ldata); |
291 | } | 291 | } |
292 | 292 | ||
293 | - function createLink(link) { | ||
294 | - var lnk = linkEndPoints(link.src, link.dst); | ||
295 | - | ||
296 | - if (!lnk) { | ||
297 | - return null; | ||
298 | - } | ||
299 | - | ||
300 | - angular.extend(lnk, { | ||
301 | - key: link.id, | ||
302 | - class: 'link', | ||
303 | - fromSource: link, | ||
304 | - | ||
305 | - // functions to aggregate dual link state | ||
306 | - type: function () { | ||
307 | - var s = lnk.fromSource, | ||
308 | - t = lnk.fromTarget; | ||
309 | - return (s && s.type) || (t && t.type) || defaultLinkType; | ||
310 | - }, | ||
311 | - online: function () { | ||
312 | - var s = lnk.fromSource, | ||
313 | - t = lnk.fromTarget, | ||
314 | - both = lnk.source.online && lnk.target.online; | ||
315 | - return both && ((s && s.online) || (t && t.online)); | ||
316 | - }, | ||
317 | - linkWidth: function () { | ||
318 | - var s = lnk.fromSource, | ||
319 | - t = lnk.fromTarget, | ||
320 | - ws = (s && s.linkWidth) || 0, | ||
321 | - wt = (t && t.linkWidth) || 0; | ||
322 | - return Math.max(ws, wt); | ||
323 | - } | ||
324 | - }); | ||
325 | - return lnk; | ||
326 | - } | ||
327 | - | ||
328 | - | ||
329 | function makeNodeKey(d, what) { | 293 | function makeNodeKey(d, what) { |
330 | var port = what + 'Port'; | 294 | var port = what + 'Port'; |
331 | return d[what] + '/' + d[port]; | 295 | return d[what] + '/' + d[port]; |
... | @@ -342,8 +306,7 @@ | ... | @@ -342,8 +306,7 @@ |
342 | .domain([1, 12]) | 306 | .domain([1, 12]) |
343 | .range([widthRatio, 12 * widthRatio]) | 307 | .range([widthRatio, 12 * widthRatio]) |
344 | .clamp(true), | 308 | .clamp(true), |
345 | - allLinkTypes = 'direct indirect optical tunnel', | 309 | + allLinkTypes = 'direct indirect optical tunnel'; |
346 | - defaultLinkType = 'direct'; | ||
347 | 310 | ||
348 | function restyleLinkElement(ldata) { | 311 | function restyleLinkElement(ldata) { |
349 | // this fn's job is to look at raw links and decide what svg classes | 312 | // this fn's job is to look at raw links and decide what svg classes |
... | @@ -568,7 +531,7 @@ | ... | @@ -568,7 +531,7 @@ |
568 | // if we are not clearing the position data (unpinning), | 531 | // if we are not clearing the position data (unpinning), |
569 | // attach the x, y, longitude, latitude... | 532 | // attach the x, y, longitude, latitude... |
570 | if (!clearPos) { | 533 | if (!clearPos) { |
571 | - ll = lngLatFromCoord([d.x, d.y]); | 534 | + ll = tms.lngLatFromCoord([d.x, d.y]); |
572 | metaUi = { | 535 | metaUi = { |
573 | x: d.x, | 536 | x: d.x, |
574 | y: d.y, | 537 | y: d.y, |
... | @@ -588,171 +551,11 @@ | ... | @@ -588,171 +551,11 @@ |
588 | $log.debug('TODO: requestTrafficForMode()...'); | 551 | $log.debug('TODO: requestTrafficForMode()...'); |
589 | } | 552 | } |
590 | 553 | ||
591 | - // ========================== | ||
592 | - // === Devices and hosts - helper functions | ||
593 | - | ||
594 | - function coordFromLngLat(loc) { | ||
595 | - var p = uplink.projection(); | ||
596 | - return p ? p([loc.lng, loc.lat]) : [0, 0]; | ||
597 | - } | ||
598 | - | ||
599 | - function lngLatFromCoord(coord) { | ||
600 | - var p = uplink.projection(); | ||
601 | - return p ? p.invert(coord) : [0, 0]; | ||
602 | - } | ||
603 | - | ||
604 | - function positionNode(node, forUpdate) { | ||
605 | - var meta = node.metaUi, | ||
606 | - x = meta && meta.x, | ||
607 | - y = meta && meta.y, | ||
608 | - xy; | ||
609 | - | ||
610 | - // If we have [x,y] already, use that... | ||
611 | - if (x && y) { | ||
612 | - node.fixed = true; | ||
613 | - node.px = node.x = x; | ||
614 | - node.py = node.y = y; | ||
615 | - return; | ||
616 | - } | ||
617 | - | ||
618 | - var location = node.location, | ||
619 | - coord; | ||
620 | - | ||
621 | - if (location && location.type === 'latlng') { | ||
622 | - coord = coordFromLngLat(location); | ||
623 | - node.fixed = true; | ||
624 | - node.px = node.x = coord[0]; | ||
625 | - node.py = node.y = coord[1]; | ||
626 | - return true; | ||
627 | - } | ||
628 | - | ||
629 | - // if this is a node update (not a node add).. skip randomizer | ||
630 | - if (forUpdate) { | ||
631 | - return; | ||
632 | - } | ||
633 | - | ||
634 | - // Note: Placing incoming unpinned nodes at exactly the same point | ||
635 | - // (center of the view) causes them to explode outwards when | ||
636 | - // the force layout kicks in. So, we spread them out a bit | ||
637 | - // initially, to provide a more serene layout convergence. | ||
638 | - // Additionally, if the node is a host, we place it near | ||
639 | - // the device it is connected to. | ||
640 | - | ||
641 | - function spread(s) { | ||
642 | - return Math.floor((Math.random() * s) - s/2); | ||
643 | - } | ||
644 | - | ||
645 | - function randDim(dim) { | ||
646 | - return dim / 2 + spread(dim * 0.7071); | ||
647 | - } | ||
648 | - | ||
649 | - function rand() { | ||
650 | - return { | ||
651 | - x: randDim(width), | ||
652 | - y: randDim(height) | ||
653 | - }; | ||
654 | - } | ||
655 | - | ||
656 | - function near(node) { | ||
657 | - var min = 12, | ||
658 | - dx = spread(12), | ||
659 | - dy = spread(12); | ||
660 | - return { | ||
661 | - x: node.x + min + dx, | ||
662 | - y: node.y + min + dy | ||
663 | - }; | ||
664 | - } | ||
665 | - | ||
666 | - function getDevice(cp) { | ||
667 | - var d = lu[cp.device]; | ||
668 | - return d || rand(); | ||
669 | - } | ||
670 | - | ||
671 | - xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand(); | ||
672 | - angular.extend(node, xy); | ||
673 | - } | ||
674 | - | ||
675 | - function createDeviceNode(device) { | ||
676 | - // start with the object as is | ||
677 | - var node = device, | ||
678 | - type = device.type, | ||
679 | - svgCls = type ? 'node device ' + type : 'node device'; | ||
680 | - | ||
681 | - // Augment as needed... | ||
682 | - node.class = 'device'; | ||
683 | - node.svgClass = device.online ? svgCls + ' online' : svgCls; | ||
684 | - positionNode(node); | ||
685 | - return node; | ||
686 | - } | ||
687 | - | ||
688 | - function createHostNode(host) { | ||
689 | - var node = host; | ||
690 | - | ||
691 | - // Augment as needed... | ||
692 | - node.class = 'host'; | ||
693 | - if (!node.type) { | ||
694 | - node.type = 'endstation'; | ||
695 | - } | ||
696 | - node.svgClass = 'node host ' + node.type; | ||
697 | - positionNode(node); | ||
698 | - return node; | ||
699 | - } | ||
700 | - | ||
701 | - function createHostLink(host) { | ||
702 | - var src = host.id, | ||
703 | - dst = host.cp.device, | ||
704 | - id = host.ingress, | ||
705 | - lnk = linkEndPoints(src, dst); | ||
706 | - | ||
707 | - if (!lnk) { | ||
708 | - return null; | ||
709 | - } | ||
710 | - | ||
711 | - // Synthesize link ... | ||
712 | - angular.extend(lnk, { | ||
713 | - key: id, | ||
714 | - class: 'link', | ||
715 | - | ||
716 | - type: function () { return 'hostLink'; }, | ||
717 | - online: function () { | ||
718 | - // hostlink target is edge switch | ||
719 | - return lnk.target.online; | ||
720 | - }, | ||
721 | - linkWidth: function () { return 1; } | ||
722 | - }); | ||
723 | - return lnk; | ||
724 | - } | ||
725 | - | ||
726 | - function linkEndPoints(srcId, dstId) { | ||
727 | - var srcNode = lu[srcId], | ||
728 | - dstNode = lu[dstId], | ||
729 | - sMiss = !srcNode ? missMsg('src', srcId) : '', | ||
730 | - dMiss = !dstNode ? missMsg('dst', dstId) : ''; | ||
731 | - | ||
732 | - if (sMiss || dMiss) { | ||
733 | - $log.error('Node(s) not on map for link:\n' + sMiss + dMiss); | ||
734 | - //logicError('Node(s) not on map for link:\n' + sMiss + dMiss); | ||
735 | - return null; | ||
736 | - } | ||
737 | - return { | ||
738 | - source: srcNode, | ||
739 | - target: dstNode, | ||
740 | - x1: srcNode.x, | ||
741 | - y1: srcNode.y, | ||
742 | - x2: dstNode.x, | ||
743 | - y2: dstNode.y | ||
744 | - }; | ||
745 | - } | ||
746 | - | ||
747 | - function missMsg(what, id) { | ||
748 | - return '\n[' + what + '] "' + id + '" missing '; | ||
749 | - } | ||
750 | 554 | ||
751 | // ========================== | 555 | // ========================== |
752 | // === Devices and hosts - D3 rendering | 556 | // === Devices and hosts - D3 rendering |
753 | 557 | ||
754 | function nodeMouseOver(m) { | 558 | function nodeMouseOver(m) { |
755 | - // TODO | ||
756 | if (!m.dragStarted) { | 559 | if (!m.dragStarted) { |
757 | $log.debug("MouseOver()...", m); | 560 | $log.debug("MouseOver()...", m); |
758 | if (hovered != m) { | 561 | if (hovered != m) { |
... | @@ -763,7 +566,6 @@ | ... | @@ -763,7 +566,6 @@ |
763 | } | 566 | } |
764 | 567 | ||
765 | function nodeMouseOut(m) { | 568 | function nodeMouseOut(m) { |
766 | - // TODO | ||
767 | if (!m.dragStarted) { | 569 | if (!m.dragStarted) { |
768 | if (hovered) { | 570 | if (hovered) { |
769 | hovered = null; | 571 | hovered = null; |
... | @@ -1031,12 +833,12 @@ | ... | @@ -1031,12 +833,12 @@ |
1031 | var node = d.el; | 833 | var node = d.el; |
1032 | node.classed('online', d.online); | 834 | node.classed('online', d.online); |
1033 | updateDeviceLabel(d); | 835 | updateDeviceLabel(d); |
1034 | - positionNode(d, true); | 836 | + tms.positionNode(d, true); |
1035 | } | 837 | } |
1036 | 838 | ||
1037 | function hostExisting(d) { | 839 | function hostExisting(d) { |
1038 | updateHostLabel(d); | 840 | updateHostLabel(d); |
1039 | - positionNode(d, true); | 841 | + tms.positionNode(d, true); |
1040 | } | 842 | } |
1041 | 843 | ||
1042 | function deviceEnter(d) { | 844 | function deviceEnter(d) { |
... | @@ -1424,9 +1226,9 @@ | ... | @@ -1424,9 +1226,9 @@ |
1424 | angular.module('ovTopo') | 1226 | angular.module('ovTopo') |
1425 | .factory('TopoForceService', | 1227 | .factory('TopoForceService', |
1426 | ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', | 1228 | ['$log', 'FnService', 'SvgUtilService', 'IconService', 'ThemeService', |
1427 | - 'FlashService', 'TopoInstService', | 1229 | + 'FlashService', 'TopoInstService', 'TopoModelService', |
1428 | 1230 | ||
1429 | - function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_) { | 1231 | + function (_$log_, _fs_, _sus_, _is_, _ts_, _flash_, _tis_, _tms_) { |
1430 | $log = _$log_; | 1232 | $log = _$log_; |
1431 | fs = _fs_; | 1233 | fs = _fs_; |
1432 | sus = _sus_; | 1234 | sus = _sus_; |
... | @@ -1434,18 +1236,24 @@ | ... | @@ -1434,18 +1236,24 @@ |
1434 | ts = _ts_; | 1236 | ts = _ts_; |
1435 | flash = _flash_; | 1237 | flash = _flash_; |
1436 | tis = _tis_; | 1238 | tis = _tis_; |
1239 | + tms = _tms_; | ||
1437 | 1240 | ||
1438 | icfg = is.iconConfig(); | 1241 | icfg = is.iconConfig(); |
1439 | 1242 | ||
1440 | // forceG is the SVG group to display the force layout in | 1243 | // forceG is the SVG group to display the force layout in |
1441 | // xlink is the cross-link api from the main topo source file | 1244 | // xlink is the cross-link api from the main topo source file |
1442 | - // w, h are the initial dimensions of the SVG | 1245 | + // dim is the initial dimensions of the SVG as [w,h] |
1443 | // opts are, well, optional :) | 1246 | // opts are, well, optional :) |
1444 | - function initForce(forceG, _uplink_, w, h, opts) { | 1247 | + function initForce(forceG, _uplink_, _dim_, opts) { |
1445 | - $log.debug('initForce().. WxH = ' + w + 'x' + h); | ||
1446 | uplink = _uplink_; | 1248 | uplink = _uplink_; |
1447 | - width = w; | 1249 | + dim = _dim_; |
1448 | - height = h; | 1250 | + |
1251 | + $log.debug('initForce().. dim = ' + dim); | ||
1252 | + | ||
1253 | + tms.initModel({ | ||
1254 | + projection: uplink.projection, | ||
1255 | + lookup: network.lookup | ||
1256 | + }, dim); | ||
1449 | 1257 | ||
1450 | settings = angular.extend({}, defaultSettings, opts); | 1258 | settings = angular.extend({}, defaultSettings, opts); |
1451 | 1259 | ||
... | @@ -1458,7 +1266,7 @@ | ... | @@ -1458,7 +1266,7 @@ |
1458 | node = nodeG.selectAll('.node'); | 1266 | node = nodeG.selectAll('.node'); |
1459 | 1267 | ||
1460 | force = d3.layout.force() | 1268 | force = d3.layout.force() |
1461 | - .size([w, h]) | 1269 | + .size(dim) |
1462 | .nodes(network.nodes) | 1270 | .nodes(network.nodes) |
1463 | .links(network.links) | 1271 | .links(network.links) |
1464 | .gravity(settings.gravity) | 1272 | .gravity(settings.gravity) |
... | @@ -1472,16 +1280,21 @@ | ... | @@ -1472,16 +1280,21 @@ |
1472 | selectObject, atDragEnd, dragEnabled, clickEnabled); | 1280 | selectObject, atDragEnd, dragEnabled, clickEnabled); |
1473 | } | 1281 | } |
1474 | 1282 | ||
1475 | - function resize(dim) { | 1283 | + function newDim(_dim_) { |
1476 | - width = dim.width; | 1284 | + dim = _dim_; |
1477 | - height = dim.height; | 1285 | + force.size(dim); |
1478 | - force.size([width, height]); | 1286 | + tms.newDim(dim); |
1479 | // Review -- do we need to nudge the layout ? | 1287 | // Review -- do we need to nudge the layout ? |
1480 | } | 1288 | } |
1481 | 1289 | ||
1290 | + function destroyForce() { | ||
1291 | + | ||
1292 | + } | ||
1293 | + | ||
1482 | return { | 1294 | return { |
1483 | initForce: initForce, | 1295 | initForce: initForce, |
1484 | - resize: resize, | 1296 | + newDim: newDim, |
1297 | + destroyForce: destroyForce, | ||
1485 | 1298 | ||
1486 | updateDeviceColors: updateDeviceColors, | 1299 | updateDeviceColors: updateDeviceColors, |
1487 | toggleHosts: toggleHosts, | 1300 | toggleHosts: toggleHosts, | ... | ... |
1 | +/* | ||
2 | + * Copyright 2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topology Model Module. | ||
19 | + Auxiliary functions for the model of the topology; that is, our internal | ||
20 | + representations of devices, hosts, links, etc. | ||
21 | + */ | ||
22 | + | ||
23 | +(function () { | ||
24 | + 'use strict'; | ||
25 | + | ||
26 | + // injected refs | ||
27 | + var $log, fs, rnd, api; | ||
28 | + | ||
29 | + var dim; // dimensions of layout, as [w,h] | ||
30 | + | ||
31 | + // configuration 'constants' | ||
32 | + var defaultLinkType = 'direct', | ||
33 | + nearDist = 15; | ||
34 | + | ||
35 | + | ||
36 | + function coordFromLngLat(loc) { | ||
37 | + var p = api.projection(); | ||
38 | + return p ? p([loc.lng, loc.lat]) : [0, 0]; | ||
39 | + } | ||
40 | + | ||
41 | + function lngLatFromCoord(coord) { | ||
42 | + var p = api.projection(); | ||
43 | + return p ? p.invert(coord) : [0, 0]; | ||
44 | + } | ||
45 | + | ||
46 | + function positionNode(node, forUpdate) { | ||
47 | + var meta = node.metaUi, | ||
48 | + x = meta && meta.x, | ||
49 | + y = meta && meta.y, | ||
50 | + xy; | ||
51 | + | ||
52 | + // If we have [x,y] already, use that... | ||
53 | + if (x && y) { | ||
54 | + node.fixed = true; | ||
55 | + node.px = node.x = x; | ||
56 | + node.py = node.y = y; | ||
57 | + return; | ||
58 | + } | ||
59 | + | ||
60 | + var location = node.location, | ||
61 | + coord; | ||
62 | + | ||
63 | + if (location && location.type === 'latlng') { | ||
64 | + coord = coordFromLngLat(location); | ||
65 | + node.fixed = true; | ||
66 | + node.px = node.x = coord[0]; | ||
67 | + node.py = node.y = coord[1]; | ||
68 | + return true; | ||
69 | + } | ||
70 | + | ||
71 | + // if this is a node update (not a node add).. skip randomizer | ||
72 | + if (forUpdate) { | ||
73 | + return; | ||
74 | + } | ||
75 | + | ||
76 | + // Note: Placing incoming unpinned nodes at exactly the same point | ||
77 | + // (center of the view) causes them to explode outwards when | ||
78 | + // the force layout kicks in. So, we spread them out a bit | ||
79 | + // initially, to provide a more serene layout convergence. | ||
80 | + // Additionally, if the node is a host, we place it near | ||
81 | + // the device it is connected to. | ||
82 | + | ||
83 | + function rand() { | ||
84 | + return { | ||
85 | + x: rnd.randDim(dim[0]), | ||
86 | + y: rnd.randDim(dim[1]) | ||
87 | + }; | ||
88 | + } | ||
89 | + | ||
90 | + function near(node) { | ||
91 | + return { | ||
92 | + x: node.x + nearDist + rnd.spread(nearDist), | ||
93 | + y: node.y + nearDist + rnd.spread(nearDist) | ||
94 | + }; | ||
95 | + } | ||
96 | + | ||
97 | + function getDevice(cp) { | ||
98 | + var d = api.lookup[cp.device]; | ||
99 | + return d || rand(); | ||
100 | + } | ||
101 | + | ||
102 | + xy = (node.class === 'host') ? near(getDevice(node.cp)) : rand(); | ||
103 | + angular.extend(node, xy); | ||
104 | + } | ||
105 | + | ||
106 | + function mkSvgCls(dh, t, on) { | ||
107 | + var ndh = 'node ' + dh, | ||
108 | + ndht = t ? ndh + ' ' + t : ndh; | ||
109 | + return on ? ndht + ' online' : ndht; | ||
110 | + } | ||
111 | + | ||
112 | + function createDeviceNode(device) { | ||
113 | + var node = device; | ||
114 | + | ||
115 | + // Augment as needed... | ||
116 | + node.class = 'device'; | ||
117 | + node.svgClass = mkSvgCls('device', device.type, device.online); | ||
118 | + positionNode(node); | ||
119 | + return node; | ||
120 | + } | ||
121 | + | ||
122 | + function createHostNode(host) { | ||
123 | + var node = host; | ||
124 | + | ||
125 | + // Augment as needed... | ||
126 | + node.class = 'host'; | ||
127 | + if (!node.type) { | ||
128 | + node.type = 'endstation'; | ||
129 | + } | ||
130 | + node.svgClass = mkSvgCls('host', node.type); | ||
131 | + positionNode(node); | ||
132 | + return node; | ||
133 | + } | ||
134 | + | ||
135 | + function createHostLink(host) { | ||
136 | + var src = host.id, | ||
137 | + dst = host.cp.device, | ||
138 | + id = host.ingress, | ||
139 | + lnk = linkEndPoints(src, dst); | ||
140 | + | ||
141 | + if (!lnk) { | ||
142 | + return null; | ||
143 | + } | ||
144 | + | ||
145 | + // Synthesize link ... | ||
146 | + angular.extend(lnk, { | ||
147 | + key: id, | ||
148 | + class: 'link', | ||
149 | + | ||
150 | + type: function () { return 'hostLink'; }, | ||
151 | + online: function () { | ||
152 | + // hostlink target is edge switch | ||
153 | + return lnk.target.online; | ||
154 | + }, | ||
155 | + linkWidth: function () { return 1; } | ||
156 | + }); | ||
157 | + return lnk; | ||
158 | + } | ||
159 | + | ||
160 | + function createLink(link) { | ||
161 | + var lnk = linkEndPoints(link.src, link.dst); | ||
162 | + | ||
163 | + if (!lnk) { | ||
164 | + return null; | ||
165 | + } | ||
166 | + | ||
167 | + angular.extend(lnk, { | ||
168 | + key: link.id, | ||
169 | + class: 'link', | ||
170 | + fromSource: link, | ||
171 | + | ||
172 | + // functions to aggregate dual link state | ||
173 | + type: function () { | ||
174 | + var s = lnk.fromSource, | ||
175 | + t = lnk.fromTarget; | ||
176 | + return (s && s.type) || (t && t.type) || defaultLinkType; | ||
177 | + }, | ||
178 | + online: function () { | ||
179 | + var s = lnk.fromSource, | ||
180 | + t = lnk.fromTarget, | ||
181 | + both = lnk.source.online && lnk.target.online; | ||
182 | + return both && ((s && s.online) || (t && t.online)); | ||
183 | + }, | ||
184 | + linkWidth: function () { | ||
185 | + var s = lnk.fromSource, | ||
186 | + t = lnk.fromTarget, | ||
187 | + ws = (s && s.linkWidth) || 0, | ||
188 | + wt = (t && t.linkWidth) || 0; | ||
189 | + return Math.max(ws, wt); | ||
190 | + } | ||
191 | + }); | ||
192 | + return lnk; | ||
193 | + } | ||
194 | + | ||
195 | + | ||
196 | + function linkEndPoints(srcId, dstId) { | ||
197 | + var srcNode = api.lookup[srcId], | ||
198 | + dstNode = api.lookup[dstId], | ||
199 | + sMiss = !srcNode ? missMsg('src', srcId) : '', | ||
200 | + dMiss = !dstNode ? missMsg('dst', dstId) : ''; | ||
201 | + | ||
202 | + if (sMiss || dMiss) { | ||
203 | + $log.error('Node(s) not on map for link:' + sMiss + dMiss); | ||
204 | + //logicError('Node(s) not on map for link:\n' + sMiss + dMiss); | ||
205 | + return null; | ||
206 | + } | ||
207 | + return { | ||
208 | + source: srcNode, | ||
209 | + target: dstNode, | ||
210 | + x1: srcNode.x, | ||
211 | + y1: srcNode.y, | ||
212 | + x2: dstNode.x, | ||
213 | + y2: dstNode.y | ||
214 | + }; | ||
215 | + } | ||
216 | + | ||
217 | + function missMsg(what, id) { | ||
218 | + return '\n[' + what + '] "' + id + '" missing'; | ||
219 | + } | ||
220 | + | ||
221 | + // ========================== | ||
222 | + // Module definition | ||
223 | + | ||
224 | + angular.module('ovTopo') | ||
225 | + .factory('TopoModelService', | ||
226 | + ['$log', 'FnService', 'RandomService', | ||
227 | + | ||
228 | + function (_$log_, _fs_, _rnd_) { | ||
229 | + $log = _$log_; | ||
230 | + fs = _fs_; | ||
231 | + rnd = _rnd_; | ||
232 | + | ||
233 | + function initModel(_api_, _dim_) { | ||
234 | + api = _api_; | ||
235 | + dim = _dim_; | ||
236 | + } | ||
237 | + | ||
238 | + function newDim(_dim_) { | ||
239 | + dim = _dim_; | ||
240 | + } | ||
241 | + | ||
242 | + return { | ||
243 | + initModel: initModel, | ||
244 | + newDim: newDim, | ||
245 | + | ||
246 | + positionNode: positionNode, | ||
247 | + createDeviceNode: createDeviceNode, | ||
248 | + createHostNode: createHostNode, | ||
249 | + createHostLink: createHostLink, | ||
250 | + createLink: createLink, | ||
251 | + coordFromLngLat: coordFromLngLat, | ||
252 | + lngLatFromCoord: lngLatFromCoord, | ||
253 | + } | ||
254 | + }]); | ||
255 | +}()); |
1 | +/* | ||
2 | + * Copyright 2014,2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Util -- Random Service - Unit Tests | ||
19 | + */ | ||
20 | +describe('factory: fw/util/random.js', function() { | ||
21 | + var rnd, $log, fs; | ||
22 | + | ||
23 | + beforeEach(module('onosUtil')); | ||
24 | + | ||
25 | + beforeEach(inject(function (RandomService, _$log_, FnService) { | ||
26 | + rnd = RandomService; | ||
27 | + $log = _$log_; | ||
28 | + fs = FnService; | ||
29 | + })); | ||
30 | + | ||
31 | + // interesting use of a custom matcher... | ||
32 | + beforeEach(function () { | ||
33 | + jasmine.addMatchers({ | ||
34 | + toBeWithinOf: function () { | ||
35 | + return { | ||
36 | + compare: function (actual, distance, base) { | ||
37 | + var lower = base - distance, | ||
38 | + upper = base + distance, | ||
39 | + result = {}; | ||
40 | + | ||
41 | + result.pass = Math.abs(actual - base) <= distance; | ||
42 | + | ||
43 | + if (result.pass) { | ||
44 | + // for negation with ".not" | ||
45 | + result.message = 'Expected ' + actual + | ||
46 | + ' to be outside ' + lower + ' and ' + | ||
47 | + upper + ' (inclusive)'; | ||
48 | + } else { | ||
49 | + result.message = 'Expected ' + actual + | ||
50 | + ' to be between ' + lower + ' and ' + | ||
51 | + upper + ' (inclusive)'; | ||
52 | + } | ||
53 | + return result; | ||
54 | + } | ||
55 | + } | ||
56 | + } | ||
57 | + }); | ||
58 | + }); | ||
59 | + | ||
60 | + it('should define RandomService', function () { | ||
61 | + expect(rnd).toBeDefined(); | ||
62 | + }); | ||
63 | + | ||
64 | + it('should define api functions', function () { | ||
65 | + expect(fs.areFunctions(rnd, [ | ||
66 | + 'spread', 'randDim' | ||
67 | + ])).toBeTruthy(); | ||
68 | + }); | ||
69 | + | ||
70 | + // really, can only do this heuristically.. hope this doesn't break | ||
71 | + it('should spread results across the range', function () { | ||
72 | + var load = 1000, | ||
73 | + s = 12, | ||
74 | + low = 0, | ||
75 | + high = 0, | ||
76 | + i, res, | ||
77 | + which = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], | ||
78 | + minCount = load / s * 0.5; // generous error | ||
79 | + | ||
80 | + for (i=0; i<load; i++) { | ||
81 | + res = rnd.spread(s); | ||
82 | + if (res < low) low = res; | ||
83 | + if (res > high) high = res; | ||
84 | + which[res + s/2]++; | ||
85 | + } | ||
86 | + expect(low).toBe(-6); | ||
87 | + expect(high).toBe(5); | ||
88 | + | ||
89 | + // check we got a good number of hits in each bucket | ||
90 | + for (i=0; i<s; i++) { | ||
91 | + expect(which[i]).toBeGreaterThan(minCount); | ||
92 | + } | ||
93 | + }); | ||
94 | + | ||
95 | + // really, can only do this heuristically.. hope this doesn't break | ||
96 | + it('should choose results across the dimension', function () { | ||
97 | + var load = 1000, | ||
98 | + dim = 100, | ||
99 | + low = 999, | ||
100 | + high = 0, | ||
101 | + i, res; | ||
102 | + | ||
103 | + for (i=0; i<load; i++) { | ||
104 | + res = rnd.randDim(dim); | ||
105 | + if (res < low) low = res; | ||
106 | + if (res > high) high = res; | ||
107 | + expect(res).toBeWithinOf(36, 50); | ||
108 | + } | ||
109 | + }); | ||
110 | +}); |
... | @@ -29,7 +29,7 @@ describe('factory: fw/util/theme.js', function() { | ... | @@ -29,7 +29,7 @@ describe('factory: fw/util/theme.js', function() { |
29 | ts.init(); | 29 | ts.init(); |
30 | })); | 30 | })); |
31 | 31 | ||
32 | - it('should define MapService', function () { | 32 | + it('should define ThemeService', function () { |
33 | expect(ts).toBeDefined(); | 33 | expect(ts).toBeDefined(); |
34 | }); | 34 | }); |
35 | 35 | ... | ... |
... | @@ -34,8 +34,11 @@ describe('factory: view/topo/topoForce.js', function() { | ... | @@ -34,8 +34,11 @@ describe('factory: view/topo/topoForce.js', function() { |
34 | 34 | ||
35 | it('should define api functions', function () { | 35 | it('should define api functions', function () { |
36 | expect(fs.areFunctions(tfs, [ | 36 | expect(fs.areFunctions(tfs, [ |
37 | - 'initForce', 'resize', 'updateDeviceColors', | 37 | + 'initForce', 'newDim', 'destroyForce', |
38 | - 'toggleHosts', 'toggleOffline','cycleDeviceLabels', 'unpin', | 38 | + |
39 | + 'updateDeviceColors', 'toggleHosts', 'toggleOffline', | ||
40 | + 'cycleDeviceLabels', 'unpin', | ||
41 | + | ||
39 | 'addDevice', 'updateDevice', 'removeDevice', | 42 | 'addDevice', 'updateDevice', 'removeDevice', |
40 | 'addHost', 'updateHost', 'removeHost', | 43 | 'addHost', 'updateHost', 'removeHost', |
41 | 'addLink', 'updateLink', 'removeLink' | 44 | 'addLink', 'updateLink', 'removeLink' | ... | ... |
1 | +/* | ||
2 | + * Copyright 2015 Open Networking Laboratory | ||
3 | + * | ||
4 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
5 | + * you may not use this file except in compliance with the License. | ||
6 | + * You may obtain a copy of the License at | ||
7 | + * | ||
8 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
9 | + * | ||
10 | + * Unless required by applicable law or agreed to in writing, software | ||
11 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
13 | + * See the License for the specific language governing permissions and | ||
14 | + * limitations under the License. | ||
15 | + */ | ||
16 | + | ||
17 | +/* | ||
18 | + ONOS GUI -- Topo View -- Topo Model Service - Unit Tests | ||
19 | + */ | ||
20 | +describe('factory: view/topo/topoModel.js', function() { | ||
21 | + var $log, fs, rnd, tms; | ||
22 | + | ||
23 | + // stop random numbers from being quite so random | ||
24 | + var mockRandom = { | ||
25 | + // mock spread returns s + 1 | ||
26 | + spread: function (s) { | ||
27 | + return s + 1; | ||
28 | + }, | ||
29 | + // mock random dimension returns d / 2 - 1 | ||
30 | + randDim: function (d) { | ||
31 | + return d/2 - 1; | ||
32 | + }, | ||
33 | + mock: 'yup' | ||
34 | + }; | ||
35 | + | ||
36 | + // to mock out the [lng,lat] <=> [x,y] transformations, we will | ||
37 | + // add/subtract 2000, 3000 respectively: | ||
38 | + // lng:2005 === x:5, lat:3004 === y:4 | ||
39 | + | ||
40 | + var mockProjection = function (lnglat) { | ||
41 | + return [lnglat[0] - 2000, lnglat[1] - 3000]; | ||
42 | + }; | ||
43 | + | ||
44 | + mockProjection.invert = function (xy) { | ||
45 | + return [xy[0] + 2000, xy[1] + 3000]; | ||
46 | + }; | ||
47 | + | ||
48 | + // our test device lookup | ||
49 | + var lu = { | ||
50 | + dev1: { | ||
51 | + 'class': 'device', | ||
52 | + id: 'dev1', | ||
53 | + x: 17, | ||
54 | + y: 27, | ||
55 | + online: true | ||
56 | + }, | ||
57 | + dev2: { | ||
58 | + 'class': 'device', | ||
59 | + id: 'dev2', | ||
60 | + x: 18, | ||
61 | + y: 28, | ||
62 | + online: true | ||
63 | + }, | ||
64 | + host1: { | ||
65 | + 'class': 'host', | ||
66 | + id: 'host1', | ||
67 | + x: 23, | ||
68 | + y: 33, | ||
69 | + cp: { | ||
70 | + device: 'dev1', | ||
71 | + port: 7 | ||
72 | + }, | ||
73 | + ingress: 'dev1/7-host1' | ||
74 | + }, | ||
75 | + host2: { | ||
76 | + 'class': 'host', | ||
77 | + id: 'host2', | ||
78 | + x: 24, | ||
79 | + y: 34, | ||
80 | + cp: { | ||
81 | + device: 'dev0', | ||
82 | + port: 0 | ||
83 | + }, | ||
84 | + ingress: 'dev0/0-host2' | ||
85 | + } | ||
86 | + }; | ||
87 | + | ||
88 | + // our test api | ||
89 | + var api = { | ||
90 | + projection: function () { return mockProjection; }, | ||
91 | + lookup: lu | ||
92 | + }; | ||
93 | + | ||
94 | + // our test dimensions and well known locations.. | ||
95 | + var dim = [20, 40], | ||
96 | + randLoc = [9, 19], // random location using randDim(): d/2-1 | ||
97 | + randHostLoc = [40, 50], // host "near" random location | ||
98 | + // given that 'nearDist' = 15 | ||
99 | + // and spread(15) = 16 | ||
100 | + // 9 + 15 + 16 = 40; 19 + 15 + 16 = 50 | ||
101 | + nearDev1 = [48,58], // [17+15+16, 27+15+16] | ||
102 | + dev1Loc = [17,27], | ||
103 | + dev2Loc = [18,28], | ||
104 | + host1Loc = [23,33], | ||
105 | + host2Loc = [24,34]; | ||
106 | + | ||
107 | + // implement some custom matchers... | ||
108 | + beforeEach(function () { | ||
109 | + jasmine.addMatchers({ | ||
110 | + toBePositionedAt: function () { | ||
111 | + return { | ||
112 | + compare: function (actual, xy) { | ||
113 | + var result = {}, | ||
114 | + actCoord = [actual.x, actual.y]; | ||
115 | + | ||
116 | + result.pass = (actual.x === xy[0]) && (actual.y === xy[1]); | ||
117 | + | ||
118 | + if (result.pass) { | ||
119 | + // for negation with ".not" | ||
120 | + result.message = 'Expected [' + actCoord + | ||
121 | + '] NOT to be positioned at [' + xy + ']'; | ||
122 | + } else { | ||
123 | + result.message = 'Expected [' + actCoord + | ||
124 | + '] to be positioned at [' + xy + ']'; | ||
125 | + } | ||
126 | + return result; | ||
127 | + } | ||
128 | + } | ||
129 | + }, | ||
130 | + toHaveEndPoints: function () { | ||
131 | + return { | ||
132 | + compare: function (actual, xy1, xy2) { | ||
133 | + var result = {}; | ||
134 | + | ||
135 | + result.pass = (actual.x1 === xy1[0]) && (actual.y1 === xy1[1]) && | ||
136 | + (actual.x2 === xy2[0]) && (actual.y2 === xy2[1]); | ||
137 | + | ||
138 | + if (result.pass) { | ||
139 | + // for negation with ".not" | ||
140 | + result.message = 'Expected ' + actual + | ||
141 | + ' NOT to have endpoints [' + xy1 + ']-[' + xy2 + ']'; | ||
142 | + } else { | ||
143 | + result.message = 'Expected ' + actual + | ||
144 | + ' to have endpoints [' + xy1 + ']-[' + xy2 + ']'; | ||
145 | + } | ||
146 | + return result; | ||
147 | + } | ||
148 | + } | ||
149 | + }, | ||
150 | + toBeFixed: function () { | ||
151 | + return { | ||
152 | + compare: function (actual) { | ||
153 | + var result = { | ||
154 | + pass: actual.fixed | ||
155 | + }; | ||
156 | + if (result.pass) { | ||
157 | + result.message = 'Expected ' + actual + | ||
158 | + ' NOT to be fixed!'; | ||
159 | + } else { | ||
160 | + result.message = 'Expected ' + actual + | ||
161 | + ' to be fixed!'; | ||
162 | + } | ||
163 | + return result; | ||
164 | + } | ||
165 | + } | ||
166 | + } | ||
167 | + }); | ||
168 | + }); | ||
169 | + | ||
170 | + beforeEach(module('ovTopo', 'onosUtil')); | ||
171 | + | ||
172 | + beforeEach(function () { | ||
173 | + module(function ($provide) { | ||
174 | + $provide.value('RandomService', mockRandom); | ||
175 | + }); | ||
176 | + }); | ||
177 | + | ||
178 | + beforeEach(inject(function (_$log_, FnService, RandomService, TopoModelService) { | ||
179 | + $log = _$log_; | ||
180 | + fs = FnService; | ||
181 | + rnd = RandomService; | ||
182 | + tms = TopoModelService; | ||
183 | + tms.initModel(api, dim); | ||
184 | + })); | ||
185 | + | ||
186 | + | ||
187 | + it('should install the mock random service', function () { | ||
188 | + expect(rnd.mock).toBe('yup'); | ||
189 | + expect(rnd.spread(4)).toBe(5); | ||
190 | + expect(rnd.randDim(8)).toBe(3); | ||
191 | + }); | ||
192 | + | ||
193 | + it('should install the mock projection', function () { | ||
194 | + expect(tms.coordFromLngLat({lng: 2005, lat: 3004})).toEqual([5,4]); | ||
195 | + expect(tms.lngLatFromCoord([5,4])).toEqual([2005,3004]); | ||
196 | + }); | ||
197 | + | ||
198 | + it('should define TopoModelService', function () { | ||
199 | + expect(tms).toBeDefined(); | ||
200 | + }); | ||
201 | + | ||
202 | + it('should define api functions', function () { | ||
203 | + expect(fs.areFunctions(tms, [ | ||
204 | + 'initModel', 'newDim', | ||
205 | + 'positionNode', 'createDeviceNode', 'createHostNode', | ||
206 | + 'createHostLink', 'createLink', | ||
207 | + 'coordFromLngLat', 'lngLatFromCoord' | ||
208 | + ])).toBeTruthy(); | ||
209 | + }); | ||
210 | + | ||
211 | + // === unit tests for positionNode() | ||
212 | + | ||
213 | + it('should position a node using meta x/y', function () { | ||
214 | + var node = { | ||
215 | + metaUi: { x:37, y:48 } | ||
216 | + }; | ||
217 | + tms.positionNode(node); | ||
218 | + expect(node).toBePositionedAt([37,48]); | ||
219 | + expect(node).toBeFixed(); | ||
220 | + }); | ||
221 | + | ||
222 | + it('should position a node by translating lng/lat', function () { | ||
223 | + var node = { | ||
224 | + location: { | ||
225 | + type: 'latlng', | ||
226 | + lng: 2008, | ||
227 | + lat: 3009 | ||
228 | + } | ||
229 | + }; | ||
230 | + tms.positionNode(node); | ||
231 | + expect(node).toBePositionedAt([8,9]); | ||
232 | + expect(node).toBeFixed(); | ||
233 | + }); | ||
234 | + | ||
235 | + it('should position a device with no location randomly', function () { | ||
236 | + var node = { 'class': 'device' }; | ||
237 | + tms.positionNode(node); | ||
238 | + expect(node).toBePositionedAt(randLoc); | ||
239 | + expect(node).not.toBeFixed(); | ||
240 | + }); | ||
241 | + | ||
242 | + it('should position a device randomly even if x/y set', function () { | ||
243 | + var node = { 'class': 'device', x: 1, y: 2 }; | ||
244 | + tms.positionNode(node); | ||
245 | + expect(node).toBePositionedAt(randLoc); | ||
246 | + expect(node).not.toBeFixed(); | ||
247 | + }); | ||
248 | + | ||
249 | + it('should NOT reposition a device randomly on update', function () { | ||
250 | + var node = { 'class': 'device', x: 1, y: 2 }; | ||
251 | + tms.positionNode(node, true); | ||
252 | + expect(node).toBePositionedAt([1,2]); | ||
253 | + expect(node).not.toBeFixed(); | ||
254 | + }); | ||
255 | + | ||
256 | + it('should position a host close to its device', function () { | ||
257 | + var node = { 'class': 'host', cp: { device: 'dev1' } }; | ||
258 | + tms.positionNode(node); | ||
259 | + | ||
260 | + // note: nearDist is 15; spread(15) adds 16; dev1 at [17,27] | ||
261 | + | ||
262 | + expect(node).toBePositionedAt(nearDev1); | ||
263 | + expect(node).not.toBeFixed(); | ||
264 | + }); | ||
265 | + | ||
266 | + it('should randomize host with no assoc device', function () { | ||
267 | + var node = { 'class': 'host', cp: { device: 'dev0' } }; | ||
268 | + tms.positionNode(node); | ||
269 | + | ||
270 | + // note: no device gives 'rand loc' [9,19] | ||
271 | + // nearDist is 15; spread(15) adds 16 | ||
272 | + | ||
273 | + expect(node).toBePositionedAt(randHostLoc); | ||
274 | + expect(node).not.toBeFixed(); | ||
275 | + }); | ||
276 | + | ||
277 | + // === unit tests for createDeviceNode() | ||
278 | + | ||
279 | + it('should create a basic device node', function () { | ||
280 | + var node = tms.createDeviceNode({ id: 'foo' }); | ||
281 | + expect(node).toBePositionedAt(randLoc); | ||
282 | + expect(node).not.toBeFixed(); | ||
283 | + expect(node.class).toEqual('device'); | ||
284 | + expect(node.svgClass).toEqual('node device'); | ||
285 | + expect(node.id).toEqual('foo'); | ||
286 | + }); | ||
287 | + | ||
288 | + it('should create device node with type', function () { | ||
289 | + var node = tms.createDeviceNode({ id: 'foo', type: 'cool' }); | ||
290 | + expect(node).toBePositionedAt(randLoc); | ||
291 | + expect(node).not.toBeFixed(); | ||
292 | + expect(node.class).toEqual('device'); | ||
293 | + expect(node.svgClass).toEqual('node device cool'); | ||
294 | + expect(node.id).toEqual('foo'); | ||
295 | + }); | ||
296 | + | ||
297 | + it('should create online device node with type', function () { | ||
298 | + var node = tms.createDeviceNode({ id: 'foo', type: 'cool', online: true }); | ||
299 | + expect(node).toBePositionedAt(randLoc); | ||
300 | + expect(node).not.toBeFixed(); | ||
301 | + expect(node.class).toEqual('device'); | ||
302 | + expect(node.svgClass).toEqual('node device cool online'); | ||
303 | + expect(node.id).toEqual('foo'); | ||
304 | + }); | ||
305 | + | ||
306 | + it('should create online device node with type and lng/lat', function () { | ||
307 | + var node = tms.createDeviceNode({ | ||
308 | + id: 'foo', | ||
309 | + type: 'yowser', | ||
310 | + online: true, | ||
311 | + location: { | ||
312 | + type: 'latlng', | ||
313 | + lng: 2048, | ||
314 | + lat: 3096 | ||
315 | + } | ||
316 | + }); | ||
317 | + expect(node).toBePositionedAt([48,96]); | ||
318 | + expect(node).toBeFixed(); | ||
319 | + expect(node.class).toEqual('device'); | ||
320 | + expect(node.svgClass).toEqual('node device yowser online'); | ||
321 | + expect(node.id).toEqual('foo'); | ||
322 | + }); | ||
323 | + | ||
324 | + // === unit tests for createHostNode() | ||
325 | + | ||
326 | + it('should create a basic host node', function () { | ||
327 | + var node = tms.createHostNode({ id: 'bar', cp: { device: 'dev0' } }); | ||
328 | + expect(node).toBePositionedAt(randHostLoc); | ||
329 | + expect(node).not.toBeFixed(); | ||
330 | + expect(node.class).toEqual('host'); | ||
331 | + expect(node.svgClass).toEqual('node host endstation'); | ||
332 | + expect(node.id).toEqual('bar'); | ||
333 | + }); | ||
334 | + | ||
335 | + it('should create a host with type', function () { | ||
336 | + var node = tms.createHostNode({ | ||
337 | + id: 'bar', | ||
338 | + type: 'classic', | ||
339 | + cp: { device: 'dev1' } | ||
340 | + }); | ||
341 | + expect(node).toBePositionedAt(nearDev1); | ||
342 | + expect(node).not.toBeFixed(); | ||
343 | + expect(node.class).toEqual('host'); | ||
344 | + expect(node.svgClass).toEqual('node host classic'); | ||
345 | + expect(node.id).toEqual('bar'); | ||
346 | + }); | ||
347 | + | ||
348 | + // === unit tests for createHostLink() | ||
349 | + | ||
350 | + it('should create a basic host link', function () { | ||
351 | + var link = tms.createHostLink(lu.host1); | ||
352 | + expect(link.source).toEqual(lu.host1); | ||
353 | + expect(link.target).toEqual(lu.dev1); | ||
354 | + expect(link).toHaveEndPoints(host1Loc, dev1Loc); | ||
355 | + expect(link.key).toEqual('dev1/7-host1'); | ||
356 | + expect(link.class).toEqual('link'); | ||
357 | + expect(link.type()).toEqual('hostLink'); | ||
358 | + expect(link.linkWidth()).toEqual(1); | ||
359 | + expect(link.online()).toEqual(true); | ||
360 | + }); | ||
361 | + | ||
362 | + it('should return null for failed endpoint lookup', function () { | ||
363 | + spyOn($log, 'error'); | ||
364 | + var link = tms.createHostLink(lu.host2); | ||
365 | + expect(link).toBeNull(); | ||
366 | + expect($log.error).toHaveBeenCalledWith( | ||
367 | + 'Node(s) not on map for link:\n[dst] "dev0" missing' | ||
368 | + ); | ||
369 | + }); | ||
370 | + | ||
371 | + // === unit tests for createLink() | ||
372 | + | ||
373 | + it('should return null for missing endpoints', function () { | ||
374 | + spyOn($log, 'error'); | ||
375 | + var link = tms.createLink({src: 'dev0', dst: 'dev00'}); | ||
376 | + expect(link).toBeNull(); | ||
377 | + expect($log.error).toHaveBeenCalledWith( | ||
378 | + 'Node(s) not on map for link:\n[src] "dev0" missing\n[dst] "dev00" missing' | ||
379 | + ); | ||
380 | + }); | ||
381 | + | ||
382 | + it('should create a basic link', function () { | ||
383 | + var linkData = { | ||
384 | + src: 'dev1', | ||
385 | + dst: 'dev2', | ||
386 | + id: 'baz', | ||
387 | + type: 'zoo', | ||
388 | + online: true, | ||
389 | + linkWidth: 1.5 | ||
390 | + }, | ||
391 | + link = tms.createLink(linkData); | ||
392 | + expect(link.source).toEqual(lu.dev1); | ||
393 | + expect(link.target).toEqual(lu.dev2); | ||
394 | + expect(link).toHaveEndPoints(dev1Loc, dev2Loc); | ||
395 | + expect(link.key).toEqual('baz'); | ||
396 | + expect(link.class).toEqual('link'); | ||
397 | + expect(link.fromSource).toBe(linkData); | ||
398 | + expect(link.type()).toEqual('zoo'); | ||
399 | + expect(link.online()).toEqual(true); | ||
400 | + expect(link.linkWidth()).toEqual(1.5); | ||
401 | + }); | ||
402 | + | ||
403 | +}); |
-
Please register or login to post a comment