/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ /*global beforeEach, afterEach */ /*global describe, it, expect, spyOn, jasmine */ /*global window, eb, loadFixtures, document */ /*global $, _, d3*/ /*global helper*/ /*global EdgeShaper*/ //////////////////////////////////////////////////////////////////////////////// /// @brief Graph functionality /// /// @file /// /// DISCLAIMER /// /// Copyright 2010-2012 triagens GmbH, Cologne, Germany /// /// 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. /// /// Copyright holder is triAGENS GmbH, Cologne, Germany /// /// @author Michael Hackstein /// @author Copyright 2011-2013, triAGENS GmbH, Cologne, Germany //////////////////////////////////////////////////////////////////////////////// (function () { "use strict"; describe('Edge Shaper', function() { var svg, getDistance = function(s, t) { var res = Math.sqrt( (t.y - s.y) * (t.y - s.y) + (t.x - s.x) * (t.x - s.x) ), m; if (s.x === t.x) { res -= t.z * 18; } else { m = Math.abs((t.y - s.y) / (t.x - s.x)); if (m < 0.4) { res -= Math.abs((res * t.z * 45) / (t.x - s.x)); } else { res -= Math.abs((res * t.z * 18) / (t.y - s.y)); } } return res; }; beforeEach(function () { svg = document.createElement("svg"); svg.id = "svg"; document.body.appendChild(svg); }); afterEach(function () { document.body.removeChild(svg); }); describe("checking the context menu", function() { var edges, shaper, fakeMenu, fakeCM; beforeEach(function() { var nodes = helper.createSimpleNodes([1, 2]), source = nodes[0], target = nodes[1], fakeMenu = {}; fakeCM = { addEntry: function() {}, bindMenu: function() {} }; edges = [ { _id: "1-2", source: source, target: target } ]; spyOn(window, "ContextMenu").andReturn(fakeCM); spyOn(fakeCM, "addEntry"); spyOn(fakeCM, "bindMenu"); shaper = new EdgeShaper(d3.select("svg")); }); it("should create the context menu", function() { expect(window.ContextMenu).toHaveBeenCalledWith("gv_edge_cm"); expect(fakeCM.addEntry).not.toHaveBeenCalled(); expect(fakeCM.bindMenu).not.toHaveBeenCalled(); shaper.drawEdges(edges); expect(fakeCM.bindMenu).toHaveBeenCalledWith($(".link")); }); it("should be able to insert an entry in the menu", function() { var lbl = "Label", func = function() {}; shaper.addMenuEntry(lbl, func); expect(fakeCM.addEntry).toHaveBeenCalledWith(lbl, jasmine.any(Function)); }); }); it('should be able to draw an edge', function () { var nodes = helper.createSimpleNodes([1, 2]), source = nodes[0], target = nodes[1], edges = [ { _id: "1-2", source: source, target: target } ], shaper = new EdgeShaper(d3.select("svg")); shaper.drawEdges(edges); expect($("svg .link line").length).toEqual(1); expect($("svg .link line")[0]).toBeDefined(); expect($("svg #1-2")[0]).toBeDefined(); }); it('should be able to draw many edges', function () { var nodes = helper.createSimpleNodes([1, 2, 3, 4]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1] }, { _id: "2-3", source: nodes[1], target: nodes[2] }, { _id: "3-4", source: nodes[2], target: nodes[3] }, { _id: "4-1", source: nodes[3], target: nodes[0] } ], shaper = new EdgeShaper(d3.select("svg")); shaper.drawEdges(edges); expect($("svg .link line").length).toEqual(4); }); it('should be able to add an event', function () { var nodes = helper.createSimpleNodes([1, 2, 3, 4]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1] }, { _id: "2-3", source: nodes[1], target: nodes[2] }, { _id: "3-4", source: nodes[2], target: nodes[3] }, { _id: "4-1", source: nodes[3], target: nodes[0] } ], clicked = [], click = function (edge) { clicked[edge.source._id] = !clicked[edge.source._id]; }, shaper = new EdgeShaper(d3.select("svg"), { actions: { click: click } }); shaper.drawEdges(edges); helper.simulateMouseEvent("click", "1-2"); helper.simulateMouseEvent("click", "3-4"); expect($("svg .link line").length).toEqual(4); expect(clicked[1]).toBeTruthy(); expect(clicked[3]).toBeTruthy(); expect(clicked[2]).toBeFalsy(); expect(clicked[4]).toBeFalsy(); }); it('should be able to unbind all events', function() { var nodes = helper.createSimpleNodes([1, 2, 3, 4]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1] }, { _id: "2-3", source: nodes[1], target: nodes[2] }, { _id: "3-4", source: nodes[2], target: nodes[3] }, { _id: "4-1", source: nodes[3], target: nodes[0] } ], clicked = [], click = function (edge) { clicked[edge.source._id] = !clicked[edge.source._id]; }, shaper = new EdgeShaper(d3.select("svg"), { actions: { click: click } }); shaper.drawEdges(edges); shaper.changeTo({ actions: { reset: true } }); helper.simulateMouseEvent("click", "1-2"); helper.simulateMouseEvent("click", "3-4"); expect($("svg .link line").length).toEqual(4); expect(clicked[1]).toBeFalsy(); expect(clicked[3]).toBeFalsy(); expect(clicked[2]).toBeFalsy(); expect(clicked[4]).toBeFalsy(); }); it('should be able to add an arrow on target side', function() { var nodes = helper.createSimpleNodes([1, 2]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1] } ], shaper = new EdgeShaper(d3.select("svg"), { shape: { type: EdgeShaper.shapes.ARROW } }); shaper.drawEdges(edges); expect($("#1-2 line").attr("marker-end")).toEqual("url(#arrow)"); expect($("svg defs marker").length).toEqual(1); expect($("svg defs #arrow").length).toEqual(1); // Orientation is important. Other layout not yet. expect($("svg defs #arrow").attr("orient")).toEqual("auto"); }); it('should position the edges correctly', function() { var center = { _id: 1, position: { x: 20, y: 20, z: 1 } }, NE = { _id: 2, position: { x: 30, y: 10, z: 1 } }, SE = { _id: 3, position: { x: 40, y: 30, z: 1 } }, SW = { _id: 4, position: { x: 10, y: 40, z: 1 } }, NW = { _id: 5, position: { x: 0, y: 0, z: 1 } }, edges = [ { _id: "1-2", source: center, target: NE }, { _id: "1-3", source: center, target: SE }, { _id: "1-4", source: center, target: SW }, { _id: "1-5", source: center, target: NW } ], shaper = new EdgeShaper(d3.select("svg"), { shape: { type: EdgeShaper.shapes.ARROW } }); shaper.drawEdges(edges); //Check Position and rotation expect($("#1-2").attr("transform")).toEqual("translate(20, 20)rotate(-45)"); expect($("#1-3").attr("transform")).toEqual("translate(20, 20)rotate(26.56505117707799)"); expect($("#1-4").attr("transform")).toEqual("translate(20, 20)rotate(116.56505117707799)"); expect($("#1-5").attr("transform")).toEqual("translate(20, 20)rotate(-135)"); //Check length of line expect($("#1-2 line").attr("x2")).toEqual(String(getDistance(center.position, NE.position))); expect($("#1-3 line").attr("x2")).toEqual(String(getDistance(center.position, SE.position))); expect($("#1-4 line").attr("x2")).toEqual(String(getDistance(center.position, SW.position))); expect($("#1-5 line").attr("x2")).toEqual(String(getDistance(center.position, NW.position))); }); it('should be able to draw an edge that follows the cursor', function() { var line, jqLine, cursorX, cursorY, nodeX = 15, nodeY = 20, shaper = new EdgeShaper(d3.select("svg")), moveCB = shaper.addAnEdgeFollowingTheCursor(nodeX, nodeY); cursorX = 20; cursorY = 30; moveCB(cursorX, cursorY); expect($("#connectionLine").length).toEqual(1); jqLine = $("#connectionLine"); line = document.getElementById("connectionLine"); expect(line.tagName.toLowerCase()).toEqual("line"); expect(jqLine.attr("x1")).toEqual(String(nodeX)); expect(jqLine.attr("y1")).toEqual(String(nodeY)); expect(jqLine.attr("x2")).toEqual(String(cursorX)); expect(jqLine.attr("y2")).toEqual(String(cursorY)); cursorX = 45; cursorY = 12; moveCB(cursorX, cursorY); expect(jqLine.attr("x2")).toEqual(String(cursorX)); expect(jqLine.attr("y2")).toEqual(String(cursorY)); }); it('should be able to remove the cursor-following edge on demand', function() { var cursorX, cursorY, nodeX = 15, nodeY = 20, shaper = new EdgeShaper(d3.select("svg")), moveCB; moveCB = shaper.addAnEdgeFollowingTheCursor(nodeX, nodeY); cursorX = 20; cursorY = 30; moveCB(cursorX, cursorY); shaper.removeCursorFollowingEdge(); expect($("#connectionLine").length).toEqual(0); }); describe('testing for colours', function() { it('should have a default colouring of no colour flag is given', function() { var nodes = helper.createSimpleNodes([1, 2]), edges = [{ _id: "1-2", source: nodes[0], target: nodes[1] },{ _id: "2-1", source: nodes[1], target: nodes[0] }], shaper = new EdgeShaper(d3.select("svg")); shaper.drawEdges(edges); expect($("#1-2 line").attr("stroke")).toEqual("#686766"); expect($("#2-1 line").attr("stroke")).toEqual("#686766"); }); it('should be able to use the same colour for all edges', function() { var nodes = helper.createSimpleNodes([1, 2]), edges = [{ _id: "1-2", source: nodes[0], target: nodes[1] },{ _id: "2-1", source: nodes[1], target: nodes[0] }], shaper = new EdgeShaper(d3.select("svg"), { color: { type: "single", stroke: "#123456" } } ); shaper.drawEdges(edges); expect($("#1-2 line").attr("stroke")).toEqual("#123456"); expect($("#2-1 line").attr("stroke")).toEqual("#123456"); }); it('should be able to use a colour based on attribute value', function() { var nodes = helper.createSimpleNodes([1, 2, 3, 4]), n1 = nodes[0], n2 = nodes[1], n3 = nodes[2], n4 = nodes[3], edges = [{ _id: "1-2", source: n1, target: n2, _data: { label: "lbl1" } },{ _id: "2-3", source: n2, target: n3, _data: { label: "lbl2" } },{ _id: "3-4", source: n3, target: n4, _data: { label: "lbl3" } },{ _id: "4-1", source: n4, target: n1, _data: { label: "lbl1" } }], shaper = new EdgeShaper(d3.select("svg"), { color: { type: "attribute", key: "label" } } ), c1,c2,c3,c4; shaper.drawEdges(edges); c1 = $("#1-2").attr("stroke"); c2 = $("#2-3").attr("stroke"); c3 = $("#3-4").attr("stroke"); c4 = $("#4-1").attr("stroke"); expect(c1).toBeDefined(); expect(c2).toBeDefined(); expect(c3).toBeDefined(); expect(c4).toBeDefined(); expect(c1).toEqual(c4); expect(c1).not.toEqual(c2); expect(c1).not.toEqual(c3); expect(c2).not.toEqual(c3); }); it('should be able to use a gradient colour', function() { var nodes = helper.createSimpleNodes([1, 2]), edges = [{ _id: "1-2", source: nodes[0], target: nodes[1] }], shaper = new EdgeShaper(d3.select("svg"), { color: { type: "gradient", source: "#123456", target: "#654321" } } ), allStops; shaper.drawEdges(edges); expect($("#1-2 line").attr("stroke")).toEqual("url(#gradientEdgeColor)"); // Sorry if the line is plain horizontal it is not displayed. expect($("#1-2 line").attr("y2")).toBeDefined(); expect($("#1-2 line").attr("y2")).not.toEqual("0"); // This check may not work //expect($("svg defs linearGradient").length).toEqual(1); expect($("svg defs #gradientEdgeColor").length).toEqual(1); allStops = $("svg defs #gradientEdgeColor stop"); expect(allStops[0].getAttribute("offset")).toEqual("0"); expect(allStops[1].getAttribute("offset")).toEqual("0.4"); expect(allStops[2].getAttribute("offset")).toEqual("0.6"); expect(allStops[3].getAttribute("offset")).toEqual("1"); expect(allStops[0].getAttribute("stop-color")).toEqual("#123456"); expect(allStops[1].getAttribute("stop-color")).toEqual("#123456"); expect(allStops[2].getAttribute("stop-color")).toEqual("#654321"); expect(allStops[3].getAttribute("stop-color")).toEqual("#654321"); }); }); describe('configured for arrow shape', function() { var shaper; beforeEach(function() { var nodes = helper.createSimpleNodes([1, 2]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1] } ]; shaper = new EdgeShaper(d3.select("svg"), { shape: { type: EdgeShaper.shapes.ARROW } }); shaper.drawEdges(edges); }); it('should clean the defs if a different shape is chosen', function() { shaper.changeTo({ shape: { type: EdgeShaper.shapes.NONE } }); expect($("#1-2").attr("marker-end")).toBeUndefined(); expect($("svg defs marker#arrow").length).toEqual(0); }); it('should not append the same marker multiple times', function() { shaper.changeTo({ shape: { type: EdgeShaper.shapes.ARROW } }); expect($("svg defs marker#arrow").length).toEqual(1); shaper.changeTo({ shape: { type: EdgeShaper.shapes.ARROW } }); expect($("svg defs marker#arrow").length).toEqual(1); }); }); describe('when edges are already drawn', function() { var edges, nodes, clicked, click = function (edge) { clicked[edge._id] = !clicked[edge._id]; }, shaper; beforeEach(function() { nodes = helper.createSimpleNodes([1, 2, 3, 4]); edges = [ {_id: "1-2", source: nodes[0], target: nodes[1]}, {_id: "2-3", source: nodes[1], target: nodes[2]}, {_id: "3-4", source: nodes[2], target: nodes[3]} ]; clicked = []; shaper = new EdgeShaper(d3.select("svg")); shaper.drawEdges(edges); expect($("svg .link").length).toEqual(3); }); it('should be able to add a click event to existing edges', function() { expect($("svg .link").length).toEqual(3); shaper.changeTo({ actions: { click: click } }); helper.simulateMouseEvent("click", "1-2"); helper.simulateMouseEvent("click", "3-4"); expect(clicked["1-2"]).toBeTruthy(); expect(clicked["3-4"]).toBeTruthy(); expect(clicked["2-3"]).toBeUndefined(); }); it('should add a click event to newly arriving edges', function() { shaper.changeTo({ actions: { click: click } }); edges.push({_id: "4-1", source: nodes[3], target: nodes[0]}); edges.push({_id: "1-3", source: nodes[0], target: nodes[2]}); shaper.drawEdges(edges); helper.simulateMouseEvent("click", "4-1"); helper.simulateMouseEvent("click", "1-3"); expect($("svg .link").length).toEqual(5); expect(clicked["4-1"]).toBeTruthy(); expect(clicked["1-3"]).toBeTruthy(); expect(clicked["1-2"]).toBeUndefined(); expect(clicked["2-3"]).toBeUndefined(); expect(clicked["3-4"]).toBeUndefined(); }); it('should display each edge exactly once if an event is added', function() { shaper.changeTo({ actions: { click: function() {return 0;} } }); expect($("svg .link").length).toEqual(3); shaper.changeTo({ actions: { click: function() {return 0;} } }); expect($("svg .link").length).toEqual(3); shaper.changeTo({ actions: { click: function() {return 0;} } }); expect($("svg .link").length).toEqual(3); }); }); describe('configured for label', function() { var shaper; beforeEach(function() { shaper = new EdgeShaper(d3.select("svg"), {"label": "label"}); }); it('should display the correct label', function() { var nodes = helper.createSimpleNodes([1, 2, 3, 4]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1], _data: { label: "first" } }, { _id: "2-3", source: nodes[1], target: nodes[2], _data: { label: "second" } }, { _id: "3-4", source: nodes[2], target: nodes[3], _data: { label: "third" } }, { _id: "4-1", source: nodes[3], target: nodes[0], _data: { label: "fourth" } } ]; shaper.drawEdges(edges); expect($("text").length).toEqual(4); expect($("#1-2 text")[0].textContent).toEqual("first"); expect($("#2-3 text")[0].textContent).toEqual("second"); expect($("#3-4 text")[0].textContent).toEqual("third"); expect($("#4-1 text")[0].textContent).toEqual("fourth"); }); it('should display the label at the correct position', function() { var nodes = [ { _id: 1, position: { x: 20, y: 20, z: 1 } }, { _id: 2, position: { x: 100, y: 20, z: 1 } } ], edges = [ { _id: "1-2", source: nodes[0], target: nodes[1], _data: { label: "first" } } ]; shaper.drawEdges(edges); expect($("#1-2 text").attr("transform")).toEqual("translate(" + getDistance(nodes[0].position, nodes[1].position) / 2 + ", -3)"); }); it('should ignore other attributes', function() { var nodes = helper.createSimpleNodes([1, 2, 3, 4]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1], _data: { "label": "correct" } }, { _id: "2-3", source: nodes[1], target: nodes[2], _data: { alt: "incorrect" } }, { _id: "3-4", source: nodes[2], target: nodes[3], _data: {} }, { _id: "4-1", source: nodes[3], target: nodes[0], _data: { "label": "correct", "alt": "incorrect" } } ]; shaper.drawEdges(edges); expect($("text").length).toEqual(4); expect($("#1-2 text")[0].textContent).toEqual("correct"); expect($("#2-3 text")[0].textContent).toEqual(""); expect($("#3-4 text")[0].textContent).toEqual(""); expect($("#4-1 text")[0].textContent).toEqual("correct"); }); it('should be able to switch to another label', function() { var nodes = helper.createSimpleNodes([1, 2]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1], _data: { label: "old" } }, { _id: "2-1", source: nodes[1], target: nodes[0], _data: { "new": "new" } } ]; shaper.drawEdges(edges); expect($("#1-2")[0].textContent).toEqual("old"); expect($("#2-1")[0].textContent).toEqual(""); shaper.changeTo({label: "new"}); expect($("#1-2")[0].textContent).toEqual(""); expect($("#2-1")[0].textContent).toEqual("new"); }); it('should be possible to toggle label display', function() { var nodes = helper.createSimpleNodes([1, 2]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1], _data: { label: "test" } } ]; shaper.drawEdges(edges); expect($("#1-2")[0].textContent).toEqual("test"); shaper.activateLabel(false); expect($("#1-2")[0].textContent).toEqual(""); expect($("#1-2 text").length).toEqual(0); shaper.activateLabel(true); expect($("#1-2")[0].textContent).toEqual("test"); }); }); describe('using a function for labels', function() { var shaper, labelFunction = function (edge) { if (edge.source._id === 1) { return "from first"; } if (edge.target._id === 3) { return "to third"; } return "default"; }; beforeEach(function() { shaper = new EdgeShaper(d3.select("svg"), {"label": labelFunction}); }); it('should display the correct label', function() { var nodes = helper.createSimpleNodes([1, 2, 3, 4]), edges = [ { _id: "1-2", source: nodes[0], target: nodes[1] }, { _id: "2-3", source: nodes[1], target: nodes[2] }, { _id: "3-4", source: nodes[2], target: nodes[3] }, { _id: "4-1", source: nodes[3], target: nodes[0] } ]; shaper.drawEdges(edges); expect($("text").length).toEqual(4); expect($("#1-2 text")[0].textContent).toEqual("from first"); expect($("#2-3 text")[0].textContent).toEqual("to third"); expect($("#3-4 text")[0].textContent).toEqual("default"); expect($("#4-1 text")[0].textContent).toEqual("default"); }); }); describe('that has already drawn some edges', function () { var shaper, edges, nodes; beforeEach(function () { shaper = new EdgeShaper(d3.select("svg")); nodes = helper.createSimpleNodes([1, 2, 3, 4]); edges = [ { _id: "1-2", source: nodes[0], target: nodes[1] }, { _id: "2-3", source: nodes[1], target: nodes[2] }, { _id: "3-4", source: nodes[2], target: nodes[3] }, { _id: "4-1", source: nodes[3], target: nodes[0] } ]; shaper.drawEdges(edges); }); it('should be able to draw more edges', function () { edges.push( { _id: "3-1", source: nodes[2], target: nodes[0] } ); edges.push({ _id: "2-4", source: nodes[1], target: nodes[3] }); shaper.drawEdges(edges); expect($("svg .link").length).toEqual(6); expect($("svg #2-3")[0]).toBeDefined(); expect($("svg #4-1")[0]).toBeDefined(); expect($("svg #3-1")[0]).toBeDefined(); expect($("svg #2-4")[0]).toBeDefined(); expect($("svg #1-2")[0]).toBeDefined(); expect($("svg #3-4")[0]).toBeDefined(); }); it('should be able to remove edges', function () { edges.splice(2, 1); edges.splice(0, 1); shaper.drawEdges(edges); expect($("svg .link").length).toEqual(2); expect($("svg #2-3")[0]).toBeDefined(); expect($("svg #4-1")[0]).toBeDefined(); expect($("svg #1-2")[0]).toBeUndefined(); expect($("svg #3-4")[0]).toBeUndefined(); }); it('should be able to add some edges and remove other edges', function () { edges.splice(2, 1); edges.splice(0, 1); edges.push( { _id: "3-1", source: nodes[2], target: nodes[0] } ); edges.push({ _id: "2-4", source: nodes[1], target: nodes[3] }); shaper.drawEdges(edges); expect($("svg .link").length).toEqual(4); expect($("svg #2-3")[0]).toBeDefined(); expect($("svg #4-1")[0]).toBeDefined(); expect($("svg #3-1")[0]).toBeDefined(); expect($("svg #2-4")[0]).toBeDefined(); expect($("svg #1-2")[0]).toBeUndefined(); expect($("svg #3-4")[0]).toBeUndefined(); }); }); describe('that has an other parent then body.svg', function () { it('should draw edges in the correct position', function () { var internalObject = d3.select("svg") .append("g") .append("svg") .append("g"), shaper = new EdgeShaper(internalObject), nodes = helper.createSimpleNodes([1, 2]), source = nodes[0], target = nodes[1], edges = [ { _id: "1-2", source: source, target: target } ]; shaper.drawEdges(edges); expect($("svg g svg g .link line").length).toEqual(1); }); }); }); }());