1
0
Fork 0
arangodb/frontend/js/graphViewer/jasmine_test/specForceLayouter/forceLayouterSpec.js

415 lines
12 KiB
JavaScript

/*jslint indent: 2, nomen: true, maxlen: 100, white: true plusplus: true */
/*global beforeEach, afterEach */
/*global describe, it, expect */
/*global runs, spyOn, waitsFor */
/*global window, eb, loadFixtures, document */
/*global $, _, d3*/
/*global ForceLayouter*/
////////////////////////////////////////////////////////////////////////////////
/// @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-2012, triAGENS GmbH, Cologne, Germany
////////////////////////////////////////////////////////////////////////////////
(function () {
"use strict";
describe('Force Layouter', function () {
var layouter,
nodes,
width,
height,
offset,
linkDistance,
gravity,
standardConfig,
edgeShaper,
createNodeList = function(amount) {
var i,
nodes = [];
for (i = 0; i < amount; i++) {
nodes.push({"id": i});
}
return nodes;
},
timeOutTest = function() {
var startTime;
runs(function() {
spyOn(layouter, 'stop').andCallThrough();
startTime = (new Date()).getTime();
layouter.start();
});
waitsFor(function() {
return layouter.stop.calls.length > 0;
}, "layouter should have been stopped", 2000);
runs(function() {
// List test if not time-outed
var endTime = (new Date()).getTime(),
duration = endTime - startTime;
expect(duration).toBeInATimeCategory();
});
},
positioningTest = function() {
runs(function() {
spyOn(layouter, 'stop').andCallThrough();
layouter.start();
});
waitsFor(function() {
return layouter.stop.calls.length > 0;
}, "layouter should have been stopped", 2000);
runs(function() {
expect(nodes).toNotBeOffScreen();
});
},
dummyNodeShaper = {
"updateNodes": function() {
var changeDist = 0;
_.each(nodes, function(d) {
changeDist += Math.abs(d.px - d.x) + Math.abs(d.py - d.y);
});
return changeDist;
}
};
beforeEach(function() {
width = 940;
height = 640;
offset = 10;
linkDistance = 100;
gravity = 0.5;
standardConfig = {
"nodes": [],
"links": [],
"width": width,
"height": height,
"gravity": gravity,
"distance": linkDistance
};
this.addMatchers({
toBeInATimeCategory: function() {
var duration = this.actual;
this.message = function(){
layouter.stop();
if (duration < 100) {
return "terminates super fast (0.1 s)";
}
if (duration < 1000) {
return "terminates fast (1 s)";
}
if (duration < 2000) {
return "terminates quite fast (2 s)";
}
if (duration < 5000) {
return "terminates in reasonable time (5 s)";
}
if (duration < 10000) {
return "terminates in acceptable time (10 s)";
}
return "does not terminate in acceptable time";
};
return duration < 10000;
},
toBeCloseToNode: function (n2, threshold) {
var n1 = this.actual,
xdis = n1.x - n2.x,
ydis = n1.y - n2.y,
distance = Math.sqrt(xdis*xdis + ydis*ydis);
this.message = function() {
return "Node " + n1.id
+ " should be close to Node " + n2.id
+ " but distance is " + distance;
};
threshold = threshold || 100;
return Math.abs(distance) < threshold;
},
toBeDistantToNode: function (n2, threshold) {
var n1 = this.actual,
xdis = n1.x - n2.x,
ydis = n1.y - n2.y,
distance = Math.sqrt(xdis*xdis + ydis*ydis);
this.message = function() {
if (distance - linkDistance < 0) {
return "Node " + n1.id
+ " should be distant from Node " + n2.id
+ " but distance is to short (" + distance + ")";
}
return "Node " + n1.id
+ " should be distant from Node " + n2.id
+ " but distance is to long (" + distance + ")";
};
threshold = threshold || 100;
return Math.abs(distance - linkDistance) < threshold;
},
toNotBeOffScreen: function () {
var minx = Number.MAX_VALUE,
miny = Number.MAX_VALUE,
maxx = Number.MIN_VALUE,
maxy = Number.MIN_VALUE,
nodes = this.actual;
_.each(nodes, function(node) {
minx = Math.min(minx, node.x);
miny = Math.min(miny, node.y);
maxx = Math.max(maxx, node.x);
maxy = Math.max(maxy, node.y);
});
this.message = function () {
var msg = "Errors: \n";
if (minx < offset) {
msg += "Minimal x: " + minx + " < " + offset + ", ";
}
if (maxx > width - offset) {
msg += "Maximal x: " + maxx + " > " + (width - offset) + ", ";
}
if (miny > offset) {
msg += "Minimal y: " + miny + " < " + offset + " ,";
}
if (maxy > height - offset) {
msg += "Maximal y: " + maxy + " > " + (height - offset);
}
return msg;
};
return minx > offset
&& maxx < width - offset
&& miny > offset
&& maxy < height - offset;
}
});
});
it('should position the first node in the centre', function() {
runs(function() {
standardConfig.nodes = createNodeList(1);
nodes = standardConfig.nodes;
edgeShaper = {"updateEdges": function(){}};
layouter = new ForceLayouter(standardConfig);
layouter.setCombinedUpdateFunction(dummyNodeShaper, edgeShaper);
spyOn(layouter, 'stop').andCallThrough();
layouter.start();
});
waitsFor(function() {
return layouter.stop.calls.length > 0;
}, "force should have been stopped", 10000);
runs(function() {
var center = {
"id": "center",
"x": width/2,
"y": height/2
};
expect(nodes[0]).toBeCloseToNode(center);
});
});
it('should position not linked nodes close to each other', function() {
runs(function() {
nodes = createNodeList(4);
standardConfig.nodes = nodes;
edgeShaper = {"updateEdges": function(){}};
layouter = new ForceLayouter(standardConfig);
layouter.setCombinedUpdateFunction(dummyNodeShaper, edgeShaper);
spyOn(layouter, 'stop').andCallThrough();
layouter.start();
});
waitsFor(function() {
return layouter.stop.calls.length > 0;
}, "force should have been stopped", 10000);
runs(function() {
expect(nodes[0]).toBeCloseToNode(nodes[1]);
expect(nodes[0]).toBeCloseToNode(nodes[2]);
expect(nodes[0]).toBeCloseToNode(nodes[3]);
expect(nodes[1]).toBeCloseToNode(nodes[2]);
expect(nodes[1]).toBeCloseToNode(nodes[3]);
expect(nodes[2]).toBeCloseToNode(nodes[3]);
});
});
it('should keep distance between linked nodes', function() {
runs(function() {
nodes = createNodeList(4);
standardConfig.nodes = nodes;
standardConfig.links = [
{"source": nodes[0], "target": nodes[1]},
{"source": nodes[0], "target": nodes[2]},
{"source": nodes[0], "target": nodes[3]},
{"source": nodes[1], "target": nodes[3]},
{"source": nodes[2], "target": nodes[3]}
];
edgeShaper = {"updateEdges": function(){}};
layouter = new ForceLayouter(standardConfig);
layouter.setCombinedUpdateFunction(dummyNodeShaper, edgeShaper);
spyOn(layouter, 'stop').andCallThrough();
layouter.start();
});
waitsFor(function() {
return layouter.stop.calls.length > 0;
}, "force should have been stopped", 10000);
runs(function() {
expect(nodes[0]).toBeDistantToNode(nodes[1]);
expect(nodes[0]).toBeDistantToNode(nodes[2]);
expect(nodes[0]).toBeDistantToNode(nodes[3]);
expect(nodes[1]).toBeDistantToNode(nodes[3]);
expect(nodes[2]).toBeDistantToNode(nodes[3]);
});
});
it('should throw an error if nodes are not defined', function() {
expect(function() {
var tmp = new ForceLayouter({"links": []});
}).toThrow("No nodes defined");
expect(function() {
var tmp = new ForceLayouter({"nodes": [],"links": []});
}).not.toThrow("No nodes defined");
});
it('should throw an error if links are not defined', function() {
expect(function() {
var tmp = new ForceLayouter({"nodes": []});
}).toThrow("No links defined");
expect(function() {
var tmp = new ForceLayouter({"nodes": [],"links": []});
}).not.toThrow("No links defined");
});
describe('tested under normal conditions (50 nodes)', function() {
beforeEach(function() {
nodes = createNodeList(50);
standardConfig.nodes = nodes;
edgeShaper = {"updateEdges": function(){}};
layouter = new ForceLayouter(standardConfig);
layouter.setCombinedUpdateFunction(dummyNodeShaper, edgeShaper);
});
it('should not position a node offscreen', function() {
positioningTest();
});
it('should terminate', function() {
timeOutTest();
});
});
describe('tested under heavy weight conditions (500 nodes)', function() {
beforeEach(function() {
nodes = createNodeList(500);
standardConfig.nodes = nodes;
edgeShaper = {"updateEdges": function(){}};
layouter = new ForceLayouter(standardConfig);
layouter.setCombinedUpdateFunction(dummyNodeShaper, edgeShaper);
});
it('should not position a node offscreen', function() {
positioningTest();
});
it('should terminate', function() {
timeOutTest();
});
});
/*
describe('tested under evil stress (5000 nodes)', function() {
beforeEach(function() {
nodes = createNodeList(5000);
standardConfig.nodes = nodes;
edgeShaper = {"updateEdges": function(){}};
layouter = new ForceLayouter(standardConfig);
layouter.setCombinedUpdateFunction(dummyNodeShaper, edgeShaper);
});
it('should not position a node offscreen', function() {
positioningTest();
});
it('should terminate', function() {
timeOutTest();
});
});
*/
/*
describe('tested by the devil himself (50000 nodes)', function() {
beforeEach(function() {
nodes = createNodeList(50000);
standardConfig.nodes = nodes;
edgeShaper = {"updateEdges": function(){}};
layouter = new ForceLayouter(standardConfig);
layouter.setCombinedUpdateFunction(dummyNodeShaper, edgeShaper);
});
it('should not position a node offscreen', function() {
positioningTest();
});
it('should terminate', function() {
timeOutTest();
});
});
*/
});
}());