jointjs
Version:
JavaScript diagramming library
444 lines (319 loc) • 14.4 kB
JavaScript
QUnit.module('routers', function(hooks) {
hooks.beforeEach(function() {
var $fixture = $('<div>', { id: 'qunit-fixture' }).appendTo(document.body);
var $paper = $('<div/>');
$fixture.append($paper);
this.graph = new joint.dia.Graph;
this.paper = new joint.dia.Paper({
el: $paper,
gridSize: 10,
model: this.graph
});
});
hooks.afterEach(function() {
this.paper.remove();
this.graph = null;
this.paper = null;
});
QUnit.test('construction', function(assert) {
var r1 = new joint.shapes.basic.Rect({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }});
var r2 = r1.clone().translate(300);
this.graph.addCell([r1, r2]);
var l0 = new joint.dia.Link({
source: { id: r1.id },
target: { id: r2.id },
router: { name: 'non-existing' }
});
assert.throws(function() {
this.graph.addCell(l0);
}, /non-existing/, 'Recognize an unexisting router.');
l0.set('router', { name: 'orthogonal' });
this.graph.addCell(l0);
assert.equal(this.graph.getLinks().length, 1, 'An orthogonal link was successfully added to the graph');
var l1 = l0.clone().set('router', { name: 'manhattan' });
this.graph.addCell(l1);
assert.equal(this.graph.getLinks().length, 2, 'A manhattan link was successfully added to the graph');
var l2 = l0.clone().set('router', { name: 'metro' });
this.graph.addCell(l2);
assert.equal(this.graph.getLinks().length, 3, 'A metro link was successfully added to the graph');
});
QUnit.test('normal routing', function(assert) {
var r1 = new joint.shapes.basic.Rect({ position: { x: 200, y: 60 }, size: { width: 50, height: 30 }});
var r2 = new joint.shapes.basic.Rect({ position: { x: 125, y: 60 }, size: { width: 50, height: 30 }});
var link = new joint.dia.Link({
source: { id: r1.id },
target: { id: r2.id },
router: { name: 'normal' },
vertices: [{ x: 150, y: 200 }]
});
this.graph.addCells([r1, r2, link]);
var linkView = this.paper.findViewByModel(link);
var pathData = linkView.$('.connection').attr('d');
assert.checkDataPath(pathData, 'M 216 90 L 150 200 L 150 90', 'link was correctly routed');
});
QUnit.test('orthogonal routing', function(assert) {
// One vertex.
var r1 = new joint.shapes.basic.Rect({ position: { x: 200, y: 60 }, size: { width: 50, height: 30 }});
var r2 = new joint.shapes.basic.Rect({ position: { x: 125, y: 60 }, size: { width: 50, height: 30 }});
var l1 = new joint.dia.Link({
source: { id: r1.id },
target: { id: r2.id },
manhattan: true,
vertices: [{ x: 150, y: 200 }]
});
this.graph.addCells([r1, r2, l1]);
var l1View = this.paper.findViewByModel(l1);
var l1PathData = l1View.$('.connection').attr('d');
assert.checkDataPath(l1PathData, 'M 225 90 L 225 200 L 150 200 L 150 90', 'link with one vertex was correctly routed');
// No vertex.
var r3 = new joint.shapes.basic.Rect({ position: { x: 40, y: 40 }, size: { width: 50, height: 30 }});
var r4 = new joint.shapes.basic.Rect({ position: { x: 220, y: 120 }, size: { width: 50, height: 30 }});
var l2 = new joint.dia.Link({
source: { id: r3.id },
target: { id: r4.id },
manhattan: true
});
this.graph.addCells([r3, r4, l2]);
var l2View = this.paper.findViewByModel(l2);
var l2PathData = l2View.$('.connection').attr('d');
assert.checkDataPath(l2PathData, 'M 90 55 L 245 55 L 245 120', 'link with no vertex was correctly routed');
// Check for spikes.
var r5 = new joint.shapes.basic.Rect({ position: { x: 200, y: 60 }, size: { width: 50, height: 30 }});
var r6 = new joint.shapes.basic.Rect({ position: { x: 350, y: 40 }, size: { width: 50, height: 30 }});
var l3 = new joint.dia.Link({
source: { id: r5.id },
target: { id: r6.id },
manhattan: true,
vertices: [{ x: 150, y: 200 }]
});
this.graph.addCells([r5, r6, l3]);
var l3View = this.paper.findViewByModel(l3);
var l3PathData = l3View.$('.connection').attr('d');
assert.checkDataPath(l3PathData, 'M 225 90 L 225 200 L 150 200 L 150 55 L 350 55', 'no spike (a return path segment) was created');
});
QUnit.test('manhattan routing', function(assert) {
var r1 = new joint.shapes.basic.Rect({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }});
var r2 = r1.clone().translate(300);
var r3 = r2.clone().translate(300);
var l0 = new joint.dia.Link({
source: { id: r1.id },
target: { id: r3.id },
router: {
name: 'manhattan', args: {
step: 20,
paddingBox: { x: 0, y: 0, width: 0, height: 0 }
}
}
});
this.graph.addCell([r1, r2, r3, l0]);
var v0 = this.paper.findViewByModel(l0);
var d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 70 L 300 70 L 300 10 L 600 10 L 600 70 L 620 70', 'Route avoids an obstacle.');
r1.translate(0, 50);
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 120 L 600 120 L 600 70 L 620 70',
'Source has been moved. Route recalculated starting from target.');
r3.translate(0, -50);
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 120 L 600 120 L 600 20 L 620 20',
'Target has been moved. Route recalculated starting from source.');
l0.set({
vertices: [],
router: {
name: 'manhattan',
args: {
step: 20,
paddingBox: { x: -20, y: -20, width: 40, height: 40 }
}
}
});
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 120 L 280 120 L 280 0 L 580 0 L 580 20 L 620 20',
'The option paddingBox was passed. The source and target element and obstacles are avoided taken this padding in account.');
var fallbackRouteSpy = sinon.spy(function() { return []; });
l0.set('router', {
name: 'manhattan',
args: {
maximumLoops: 1,
fallbackRoute: fallbackRouteSpy
}
});
assert.ok(fallbackRouteSpy.calledOnce);
l0.set('router', {
name: 'manhattan',
args: {
maximumLoops: 1,
step: 20,
paddingBox: { x: 0, y: 0, width: 0, height: 0 }
}
});
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 120 L 680 120 L 680 60',
'The default fallback router made an orthogonal link.');
l0.set({
vertices: [{ x: 20, y: 20 }],
router: {
name: 'manhattan',
args: {
step: 20,
paddingBox: { x: 0, y: 0, width: 0, height: 0 }
}
}
});
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 80 80 L 80 20 L 20 20 L 20 0 L 600 0 L 600 20 L 620 20',
'A vertex was added. Route correctly recalculated.');
l0.set({
vertices: [{ x: 21, y: 21 }]
});
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 80 80 L 80 21 L 21 21 L 21 20 L 620 20',
'A vertex was moved (not snapped to the grid now). Route correctly recalculated.');
var draggingRouteSpy = sinon.spy();
l0.set({
target: { x: 200, y: 200 },
router: {
name: 'manhattan',
args: {
draggingRoute: draggingRouteSpy
}
}
});
assert.ok(draggingRouteSpy.calledOnce);
l0.set({
target: { id: r3.id },
vertices: [],
router: {
name: 'manhattan',
args: {
excludeTypes: ['basic.Rect'],
step: 20,
paddingBox: { x: 0, y: 0, width: 0, height: 0 }
}
}
});
r1.translate(0, -50);
r3.translate(0, 50);
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 70 L 620 70',
'Set excludeTypes parameter to "basic.Rect" makes routing ignore those shapes.');
r2.remove();
l0.set({
vertices: [{ x: 800, y: 80 }],
router: {
name: 'manhattan',
args: {
excludeEnds: ['target'],
step: 20,
paddingBox: { x: 0, y: 0, width: 0, height: 0 }
}
}
});
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 70 L 800 70 L 800 80 L 760 80 L 760 70 L 740 70',
'Set excludeEnds parameter to "target" makes routing ignore target element.');
l0.set({
vertices: [],
router: {
name: 'manhattan',
args: {
startDirections: ['left'],
endDirections: ['right'],
step: 20,
paddingBox: { x: 0, y: 0, width: 0, height: 0 }
}
}
});
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 20 70 L 0 70 L 0 10 L 760 10 L 760 70 L 740 70',
'Set startDirections & endDirections parameters makes routing starts and ends from/to the given direction.');
var spyIsPointObstacle = sinon.spy(function() { return false; });
l0.set({
vertices: [],
router: {
name: 'manhattan',
args: {
isPointObstacle: spyIsPointObstacle
}
}
});
d = v0.$('.connection').attr('d');
assert.ok(spyIsPointObstacle.called);
assert.ok(spyIsPointObstacle.alwaysCalledWithExactly(sinon.match.instanceOf(g.Point)));
assert.checkDataPath(d, 'M 140 70 L 620 70','isPointObstacle option is taken into account');
});
QUnit.test('metro routing', function(assert) {
var r1 = new joint.shapes.basic.Rect({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }});
var r2 = r1.clone().translate(300, 300);
var r3 = r2.clone().translate(300, 300);
var l0 = new joint.dia.Link({
source: { id: r1.id },
target: { id: r3.id },
router: {
name: 'metro',
args: {
step: 20,
paddingBox: { x: 0, y: 0, width: 0, height: 0 }
}
}
});
this.graph.addCell([r1, r2, r3, l0]);
var v0 = this.paper.findViewByModel(l0);
var d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 70 L 160 70 L 400 310 L 440 310 L 680 550 L 680 630',
'Route avoids an obstacle.');
l0.set('router', {
name: 'metro',
args: {
maximumLoops: 1
}
});
d = v0.$('.connection').attr('d');
assert.checkDataPath(d, 'M 80 70 L 81 70 L 680 670 L 680 670',
'The default fallback router made a metro link.');
});
QUnit.test('oneSide routing', function(assert) {
var r1 = new joint.shapes.basic.Rect({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }});
var r2 = r1.clone().translate(300, 300);
var l = new joint.dia.Link({ source: { id: r1.id }, target: { id: r2.id }});
this.graph.addCell([r1, r2, l]);
var v = this.paper.findViewByModel(l);
// Left side
l.set('router', { name: 'oneSide', args: { padding: 20, side: 'left' }});
var d = v.$('.connection').attr('d');
assert.checkDataPath(d, 'M 20 70 L 0 70 L 0 370 L 320 370', 'Route goes only on the left side.');
// Padding option
l.set('router', { name: 'oneSide', args: { padding: 40, side: 'left' }});
d = v.$('.connection').attr('d');
assert.checkDataPath(d, 'M 20 70 L -20 70 L -20 370 L 320 370', 'Route respects the padding.');
// Right side
l.set('router', { name: 'oneSide', args: { padding: 40, side: 'right' }});
d = v.$('.connection').attr('d');
assert.checkDataPath(d, 'M 140 70 L 480 70 L 480 370 L 440 370', 'Route goes only on the right side.');
// Top side
l.set('router', { name: 'oneSide', args: { padding: 40, side: 'top' }});
d = v.$('.connection').attr('d');
assert.checkDataPath(d, 'M 80 30 L 80 -10 L 380 -10 L 380 330', 'Route goes only on the top.');
// Bottom side
l.set('router', { name: 'oneSide', args: { padding: 40, side: 'bottom' }});
d = v.$('.connection').attr('d');
assert.checkDataPath(d, 'M 80 110 L 80 450 L 380 450 L 380 410', 'Route goes only on the bottom');
// Wrong side specified
assert.throws(function() {
l.set('router', { name: 'oneSide', args: { padding: 40, side: 'non-existing' }});
}, 'An error is thrown when a non-existing side is provided.');
});
QUnit.test('custom routing', function(assert) {
var r1 = new joint.shapes.basic.Rect({ position: { x: 20, y: 30 }, size: { width: 120, height: 80 }});
var r2 = r1.clone().translate(300, 300);
var l = new joint.dia.Link({ source: { id: r1.id }, target: { id: r2.id }});
this.graph.addCell([r1, r2, l]);
var called = 0;
l.set('router', function(oldVertices) {
assert.deepEqual(oldVertices, []);
called += 1;
return oldVertices;
});
assert.equal(called, 1);
});
});