/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */ /*global beforeEach, afterEach */ /*global describe, it, expect, jasmine*/ /*global runs, waitsFor, spyOn, waits */ /*global window, eb, loadFixtures, document */ /*global $, _, d3*/ /*global helper, mocks, JSONAdapter:true*/ /*global GraphViewerUI, NodeShaper, 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('Graph Viewer UI', function () { var div, ui, adapterConfig, adapterMockCall; beforeEach(function() { //Mock for jsonAdapter window.communicationMock(spyOn); var Tmp = JSONAdapter; JSONAdapter = function (jsonPath, nodes, edges, width, height) { var r = new Tmp(jsonPath, nodes, edges, width, height); r.loadNodeFromTreeByAttributeValue = function(attribute, value, callback) { adapterMockCall = { attribute: attribute, value: value }; }; r.getCollections = function(callback) { callback(["nodes"], ["edges"]); }; return r; }; //Mock for ZoomManager if (window.ZoomManager === undefined) { window.ZoomManager = {}; } spyOn(window, "ZoomManager"); div = document.createElement("div"); div.id = "contentDiv"; div.style.width = "200px"; div.style.height = "200px"; adapterConfig = {type: "json", path: "../test_data/"}; document.body.appendChild(div); ui = new GraphViewerUI(div, adapterConfig); this.addMatchers({ toBeTag: function(name) { var el = this.actual; this.message = function() { return "Expected " + el.tagName.toLowerCase() + " to be a " + name; }; return el.tagName.toLowerCase() === name; }, toBeOfClass: function(name) { var el = this.actual; this.message = function() { return "Expected " + el.className + " to be " + name; }; return el.className === name; }, toBeADropdownMenu: function() { var div = this.actual, btn = div.children[0], list = div.children[1], msg = ""; this.message = function() { return "Expected " + msg; }; expect(div).toBeOfClass("btn-group"); expect(btn).toBeTag("button"); expect(btn).toBeOfClass("btn btn-inverse btn-small dropdown-toggle"); if (btn.getAttribute("data-toggle") !== "dropdown") { msg = "first elements data-toggle to be dropdown"; return false; } if (btn.children[0].tagName.toLowerCase() !== "span" && btn.children[0].getAttribute("class") !== "caret") { msg = "first element to contain a caret"; return false; } // Check the list if (list.getAttribute("class") !== "dropdown-menu") { msg = "list element to be of class dropdown-menu"; return false; } return true; } }); }); afterEach(function() { document.body.removeChild(div); }); it('should throw an error if no container element is given', function() { expect( function() { var t = new GraphViewerUI(); } ).toThrow("A parent element has to be given."); }); it('should throw an error if the container element has no id', function() { expect( function() { var t = new GraphViewerUI(document.createElement("div")); } ).toThrow("The parent element needs an unique id."); }); it('should throw an error if the adapter config is not given', function() { expect( function() { var t = new GraphViewerUI(div); } ).toThrow("An adapter configuration has to be given"); }); it('should append a svg to the given parent', function() { expect($("#contentDiv svg").length).toEqual(1); }); it('should automatically start the ZoomManager', function() { expect(window.ZoomManager).toHaveBeenCalledWith( 140, 200, jasmine.any(Object), jasmine.any(Object), jasmine.any(NodeShaper), jasmine.any(EdgeShaper), {}, jasmine.any(Function) ); }); describe('checking the toolbox', function() { var toolboxSelector = "#contentDiv #toolbox"; beforeEach(function() { this.addMatchers({ toConformToToolboxLayout: function() { var box = this.actual; expect(box).toBeTag("div"); expect(box.id).toEqual("toolbox"); expect(box).toBeOfClass("btn-group btn-group-vertical pull-left toolbox"); _.each(box.children, function(group) { expect(group).toBeTag("div"); expect(group).toBeOfClass("btn btn-group"); expect(group.children.length).toEqual(2); // Correctness of buttons is checked in eventDispatcherUISpec. }); return true; } }); }); it('should append the toolbox', function() { expect($(toolboxSelector).length).toEqual(1); }); it('should contain the objects from eventDispatcher', function() { expect($(toolboxSelector + " #control_event_drag").length).toEqual(1); expect($(toolboxSelector + " #control_event_edit").length).toEqual(1); expect($(toolboxSelector + " #control_event_expand").length).toEqual(1); expect($(toolboxSelector + " #control_event_delete").length).toEqual(1); expect($(toolboxSelector + " #control_event_connect").length).toEqual(1); }); it('should have the correct layout', function() { expect($(toolboxSelector)[0]).toConformToToolboxLayout(); }); it('should create the additional mouse-icon box', function() { var pointerBox = $("#contentDiv #mousepointer"); expect(pointerBox.length).toEqual(1); expect(pointerBox[0]).toBeTag("div"); expect(pointerBox[0]).toBeOfClass("mousepointer"); }); it('should position the mouse-icon box next to the mouse pointer', function() { var x = 40, y = 50, pointerBox = $("#contentDiv #mousepointer"); helper.simulateMouseMoveEvent("graphViewerSVG", x, y); expect(pointerBox.offset().left).toEqual(x + 7); expect(pointerBox.offset().top).toEqual(y + 12); x = 66; y = 33; helper.simulateMouseMoveEvent("graphViewerSVG", x, y); expect(pointerBox.offset().left).toEqual(x + 7); expect(pointerBox.offset().top).toEqual(y + 12); }); }); describe('checking the menubar', function() { it('should append the menubar', function() { expect($("#contentDiv #menubar").length).toEqual(1); }); it('should contain a field to load a node by attribute', function() { var barSelector = "#contentDiv #menubar", attrfield = $(barSelector + " #attribute")[0], valfield = $(barSelector + " #value")[0], btn = $(barSelector + " #loadnode")[0]; expect($(barSelector + " #attribute").length).toEqual(1); expect($(barSelector + " #value").length).toEqual(1); expect($(barSelector + " #loadnode").length).toEqual(1); expect(attrfield).toBeTag("input"); expect(attrfield.type).toEqual("text"); expect(attrfield.className).toEqual("input-mini searchByAttribute"); expect(attrfield.placeholder).toEqual("key"); expect(valfield).toBeTag("input"); expect(valfield.type).toEqual("text"); expect(valfield.className).toEqual("searchInput"); expect(valfield.placeholder).toEqual("value"); expect(btn).toBeTag("img"); expect(btn.width).toEqual(16); expect(btn.height).toEqual(16); expect(btn.className).toEqual("searchSubmit"); }); it('should contain a position for the layout buttons', function() { expect($("#contentDiv #menubar #modifiers").length).toEqual(1); expect($("#contentDiv #menubar #modifiers")[0].className).toEqual("pull-right"); }); /* it('should contain a menu for the node shapes', function() { var menuSelector = "#contentDiv #menubar #nodeshapermenu"; expect($(menuSelector).length).toEqual(1); expect($(menuSelector)[0]).toBeADropdownMenu(); expect($(menuSelector + " #control_none").length).toEqual(1); expect($(menuSelector + " #control_circle").length).toEqual(1); expect($(menuSelector + " #control_rect").length).toEqual(1); expect($(menuSelector + " #control_label").length).toEqual(1); expect($(menuSelector + " #control_singlecolour").length).toEqual(1); expect($(menuSelector + " #control_attributecolour").length).toEqual(1); expect($(menuSelector + " #control_expandcolour").length).toEqual(1); }); */ /* it('should contain a menu for the edge shapes', function() { var menuSelector = "#contentDiv #menubar #edgeshapermenu"; expect($(menuSelector).length).toEqual(1); expect($(menuSelector)[0]).toBeADropdownMenu(); expect($(menuSelector + " #control_none").length).toEqual(1); expect($(menuSelector + " #control_arrow").length).toEqual(1); expect($(menuSelector + " #control_label").length).toEqual(1); expect($(menuSelector + " #control_singlecolour").length).toEqual(1); expect($(menuSelector + " #control_attributecolour").length).toEqual(1); expect($(menuSelector + " #control_gradientcolour").length).toEqual(1); }); */ /* it('should contain a menu for the adapter', function() { var menuSelector = "#contentDiv #menubar #adaptermenu"; expect($(menuSelector).length).toEqual(1); expect($(menuSelector)[0]).toBeADropdownMenu(); expect($(menuSelector + " #control_collections").length).toEqual(1); }); */ /* it('should contain a menu for the layouter', function() { var menuSelector = "#contentDiv #menubar #layoutermenu"; expect($(menuSelector).length).toEqual(1); expect($(menuSelector)[0]).toBeADropdownMenu(); expect($(menuSelector + " #control_gravity").length).toEqual(1); expect($(menuSelector + " #control_distance").length).toEqual(1); expect($(menuSelector + " #control_charge").length).toEqual(1); }); */ it('should contain a general configure menu', function() { var menuSelector = "#contentDiv #menubar #configuremenu"; expect($(menuSelector).length).toEqual(1); expect($(menuSelector)[0]).toBeADropdownMenu(); expect($("> button", menuSelector).text()).toEqual("Configure "); expect($(menuSelector + " #control_adapter_collections").length).toEqual(1); expect($(menuSelector + " #control_node_labelandcolour").length).toEqual(1); }); it('should have the same layout as the web interface', function() { var header = div.children[0], transHeader = header.firstChild, searchField = transHeader.children[0], content = div.children[1]; expect(header).toBeTag("ul"); expect(header.id).toEqual("menubar"); expect(header.className).toEqual("thumbnails2"); expect(transHeader).toBeTag("div"); expect(transHeader.id).toEqual("transparentHeader"); expect(searchField).toBeTag("div"); expect(searchField.id).toEqual("transparentPlaceholder"); expect(searchField.className).toEqual("pull-left"); expect(searchField.children[0].id).toEqual("attribute"); expect(searchField.children[1]).toBeTag("span"); expect(searchField.children[1].textContent).toEqual("=="); expect(searchField.children[2].id).toEqual("value"); expect(searchField.children[3].id).toEqual("loadnode"); }); }); describe('checking to load a graph', function() { var waittime = 200; beforeEach(function() { this.addMatchers({ toBeDisplayed: function() { var nodes = this.actual, nonDisplayed = []; this.message = function(){ var msg = "Nodes: ["; _.each(nonDisplayed, function(n) { msg += n + " "; }); msg += "] are not displayed."; return msg; }; _.each(nodes, function(n) { if ($("svg #" + n)[0] === undefined) { nonDisplayed.push(n); } }); return nonDisplayed.length === 0; } }); }); it('should load the graph by _id', function() { runs(function() { $("#contentDiv #menubar #value").attr("value", "0"); helper.simulateMouseEvent("click", "loadnode"); }); waits(waittime); runs(function() { expect([0, 1, 2, 3, 4]).toBeDisplayed(); }); }); it('should load the graph on pressing enter', function() { runs(function() { $("#contentDiv #menubar #value").attr("value", "0"); var e = $.Event("keypress"); e.keyCode = 13; $("#value").trigger(e); }); waits(waittime); runs(function() { expect([0, 1, 2, 3, 4]).toBeDisplayed(); }); }); it('should load the graph by attribute and value', function() { runs(function() { adapterMockCall = {}; $("#contentDiv #menubar #attribute").attr("value", "name"); $("#contentDiv #menubar #value").attr("value", "0"); helper.simulateMouseEvent("click", "loadnode"); expect(adapterMockCall).toEqual({ attribute: "name", value: "0" }); }); }); }); describe('set up with jsonAdapter and click Expand rest default', function() { // This waittime is rather optimistic, on a slow machine this has to be increased var waittime = 100, clickOnNode = function(id) { helper.simulateMouseEvent("click", id); }; beforeEach(function() { this.addMatchers({ toBeDisplayed: function() { var nodes = this.actual, nonDisplayed = []; this.message = function(){ var msg = "Nodes: ["; _.each(nonDisplayed, function(n) { msg += n + " "; }); msg += "] are not displayed."; return msg; }; _.each(nodes, function(n) { if ($("svg #" + n)[0] === undefined) { nonDisplayed.push(n); } }); return nonDisplayed.length === 0; }, toNotBeDisplayed: function() { var nodes = this.actual, displayed = []; this.message = function() { var msg = "Nodes: ["; _.each(displayed, function(n) { msg += n + " "; }); msg += "] are still displayed."; return msg; }; _.each(nodes, function(n) { if ($("svg #" + n)[0] !== undefined) { displayed.push(n); } }); return displayed.length === 0; }, toBeConnectedTo: function(target) { var source = this.actual; this.message = function() { return source + " -> " + target + " edge, does not exist"; }; return $("svg #" + source + "-" + target)[0] !== undefined; }, toNotBeConnectedTo: function(target) { var source = this.actual; this.message = function() { return source + " -> " + target + " edge, does still exist"; }; return $("svg #" + source + "-" + target)[0] === undefined; } }); runs (function() { $("#contentDiv #menubar #value").attr("value", "0"); helper.simulateMouseEvent("click", "loadnode"); helper.simulateMouseEvent("click", "control_event_expand"); }); waits(waittime); }); it("should be able to expand a node", function() { runs (function() { clickOnNode(1); }); waits(waittime); runs (function() { expect([0, 1, 2, 3, 4, 5, 6, 7]).toBeDisplayed(); }); }); it("should be able to collapse the root", function() { runs (function() { clickOnNode(0); }); waits(waittime); runs (function() { // Load 1 Nodes: Root expect([0]).toBeDisplayed(); expect([1, 2, 3, 4]).toNotBeDisplayed(); }); }); it("should be able to load a different graph", function() { runs (function() { $("#contentDiv #menubar #value").attr("value", "42"); helper.simulateMouseEvent("click", "loadnode"); }); waits(waittime); runs (function() { expect([42, 43, 44, 45]).toBeDisplayed(); expect([0, 1, 2, 3, 4]).toNotBeDisplayed(); }); }); describe("when a user rapidly expand nodes", function() { beforeEach(function() { runs (function() { clickOnNode(1); clickOnNode(2); clickOnNode(3); clickOnNode(4); }); waits(waittime); it("the graph should still be correct", function() { expect([0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 12]).toBeDisplayed(); }); }); }); describe("when a user rapidly expand and collapses nodes", function() { beforeEach(function() { runs (function() { clickOnNode(1); clickOnNode(2); clickOnNode(3); clickOnNode(4); }); // Wait a gentle second for all nodes to expand properly waits(waittime); runs(function() { clickOnNode(1); clickOnNode(4); }); waits(waittime); }); it("the graph should still be correct", function() { expect([0, 1, 2, 3, 4, 8, 9]).toBeDisplayed(); expect([5, 6, 7, 12]).toNotBeDisplayed(); }); }); describe("when an undirected circle has been loaded", function() { beforeEach(function() { runs (function() { clickOnNode(2); clickOnNode(3); }); waits(waittime); }); it("the basis should be correct", function() { expect([0, 1, 2, 3, 4, 8, 9]).toBeDisplayed(); }); it("should be able to collapse one node " + "without removing the double referenced one", function() { runs (function() { clickOnNode(2); }); waits(waittime); runs (function() { expect([2, 3, 8]).toBeDisplayed(); expect(2).toNotBeConnectedTo(8); expect(3).toBeConnectedTo(8); }); }); it("should be able to collapse the other node " + "without removing the double referenced one", function() { runs (function() { clickOnNode(3); }); waits(waittime); runs (function() { expect([2, 3, 8]).toBeDisplayed(); expect(3).toNotBeConnectedTo(8); expect(2).toBeConnectedTo(8); }); }); it("should be able to collapse the both nodes " + "and remove the double referenced one", function() { runs (function() { clickOnNode(3); clickOnNode(2); }); waits(waittime); runs (function() { expect([2, 3]).toBeDisplayed(); expect([8]).toNotBeDisplayed(); expect(3).toNotBeConnectedTo(8); expect(2).toNotBeConnectedTo(8); }); }); }); describe("when a complex graph has been loaded", function() { beforeEach(function() { runs(function() { clickOnNode(1); clickOnNode(4); clickOnNode(2); clickOnNode(3); }); waits(waittime); }); it("should be able to collapse a node " + "referencing a node connected to a subgraph", function() { runs(function() { clickOnNode(1); }); waits(waittime); runs(function() { expect([0, 1, 2, 3, 4, 5, 8, 9, 12]).toBeDisplayed(); expect([6, 7, 10, 11]).toNotBeDisplayed(); expect(0).toBeConnectedTo(1); expect(0).toBeConnectedTo(2); expect(0).toBeConnectedTo(3); expect(0).toBeConnectedTo(4); expect(1).toNotBeConnectedTo(5); expect(2).toBeConnectedTo(8); expect(3).toBeConnectedTo(8); expect(3).toBeConnectedTo(9); expect(4).toBeConnectedTo(5); expect(4).toBeConnectedTo(12); }); }); }); }); }); }());