jointjs
Version:
JavaScript diagramming library
1,268 lines (958 loc) • 98.6 kB
JavaScript
QUnit.module('paper', 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.module('Dimensions', function(hooks) {
var $container;
hooks.beforeEach(function() {
var $paper = this.paper.$el;
$container = $('<div>').css('display', 'inline-block');
$paper.parent().append($container.append($paper));
});
hooks.afterEach(function() {
$container.remove();
});
QUnit.test('default', function(assert) {
var paper = this.paper;
var size = paper.getComputedSize();
assert.equal(size.width, 800);
assert.equal(size.height, 600);
});
QUnit.test('number', function(assert) {
var WIDTH = 100;
var HEIGHT = 200;
var paper = this.paper;
paper.setDimensions(WIDTH, HEIGHT);
assert.equal(paper.options.width, WIDTH);
assert.equal(paper.options.height, HEIGHT);
var size = paper.getComputedSize();
assert.equal(size.width, WIDTH);
assert.equal(size.height, HEIGHT);
});
QUnit.test('string', function(assert) {
var WIDTH = '100%';
var HEIGHT = '50%';
var paper = this.paper;
$container.css({ width: 100, height: 200 });
paper.setDimensions(WIDTH, HEIGHT);
assert.equal(paper.options.width, WIDTH);
assert.equal(paper.options.height, HEIGHT);
var size = paper.getComputedSize();
assert.equal(size.width, 100);
assert.equal(size.height, 100);
});
QUnit.test('null', function(assert) {
var paper = this.paper;
paper.setDimensions(null, null);
assert.equal(paper.options.width, null);
assert.equal(paper.options.height, null);
var size = paper.getComputedSize();
assert.equal(size.width, paper.$el.width());
assert.equal(size.height, paper.$el.height());
});
});
QUnit.test('paper.addCell() number of sortViews()', function(assert) {
var spy = sinon.spy(this.paper, 'sortViews');
var r1 = new joint.shapes.basic.Rect;
var r2 = new joint.shapes.basic.Rect;
var r3 = new joint.shapes.basic.Rect;
this.graph.addCell(r1);
assert.equal(spy.callCount, 1, 'sort the views one time per each addCell()');
this.graph.addCell(r2);
assert.equal(spy.callCount, 2, 'sort the views one time per each addCell()');
this.graph.addCell(r3);
assert.equal(spy.callCount, 3, 'sort the views one time per each addCell()');
});
QUnit.test('paper.addCells() number of sortViews()', function(assert) {
var spy = sinon.spy(this.paper, 'sortViews');
var r1 = new joint.shapes.basic.Rect;
var r2 = new joint.shapes.basic.Rect;
var r3 = new joint.shapes.basic.Rect;
var r4 = new joint.shapes.basic.Rect;
this.graph.addCells([r1, r2]);
assert.equal(spy.callCount, 1, 'sort the views one time per each addCells()');
this.graph.addCells([r3, r4]);
assert.equal(spy.callCount, 2, 'sort the views one time per each addCells()');
});
QUnit.test('async paper.addCells() should not throw on non-flat array', function(assert) {
this.paper.options.async = true;
this.paper.unfreeze();
assert.expect(2);
var done = assert.async();
var a = new joint.shapes.basic.Rect;
var b = new joint.shapes.basic.Rect;
var c = new joint.shapes.basic.Rect;
this.paper.on('render:done', function() {
assert.equal(this.graph.getCells().length, 3);
assert.equal(this.paper.findViewsInArea(g.rect(-10, -10, 500, 500)).length, 3);
done();
}, this);
this.paper.model.addCells([[a], [b, [c]]]);
});
QUnit.test('paper.resetViews()', function(assert) {
var r1 = new joint.shapes.basic.Rect;
var r2 = new joint.shapes.basic.Rect;
var r3 = new joint.shapes.basic.Rect;
var viewport = V(this.paper.cells);
viewport.append(V('rect').addClass('not-a-cell'));
this.graph.addCell(r1);
var r1View = this.paper.findViewByModel(r1);
var $r1 = r1View.$el;
this.graph.resetCells([r2, r3]);
assert.equal(this.graph.get('cells').length, 2, 'previous cells were removed from the graph after calling graph.resetCells()');
assert.ok(!$r1 || !$.contains(this.paper.$el[0], $r1[0]), 'previous cells were removed from the paper after calling graph.resetCells()');
assert.equal(viewport.find('.not-a-cell').length, 1, 'should not remove non-cell DOM elements from viewport');
});
QUnit.test('graph.fromJSON(), graph.toJSON()', function(assert) {
var json = JSON.parse('{"cells":[{"type":"basic.Circle","size":{"width":100,"height":60},"position":{"x":110,"y":480},"id":"bbb9e641-9756-4f42-997a-f4818b89f374","embeds":"","z":0},{"type":"link","source":{"id":"bbb9e641-9756-4f42-997a-f4818b89f374"},"target":{"id":"cbd1109e-4d34-4023-91b0-f31bce1318e6"},"id":"b4289c08-07ea-49d2-8dde-e67eb2f2a06a","z":1},{"type":"basic.Rect","position":{"x":420,"y":410},"size":{"width":100,"height":60},"id":"cbd1109e-4d34-4023-91b0-f31bce1318e6","embeds":"","z":2}]}');
this.graph.fromJSON(json);
assert.equal(this.graph.get('cells').length, 3, 'all the cells were reconstructed from JSON');
// Check that the link is before the last cell in the DOM. This check is there because
// paper might have resorted the cells so that links are always AFTER elements.
var linkView = this.paper.findViewByModel('b4289c08-07ea-49d2-8dde-e67eb2f2a06a');
var rectView = this.paper.findViewByModel('cbd1109e-4d34-4023-91b0-f31bce1318e6');
var circleView = this.paper.findViewByModel('bbb9e641-9756-4f42-997a-f4818b89f374');
assert.ok(rectView.el.previousSibling === linkView.el, 'link view is before rect element in the DOM');
assert.ok(linkView.el.previousSibling === circleView.el, 'link view is after circle element in the DOM');
this.graph.fromJSON(this.graph.toJSON());
assert.equal(this.graph.get('cells').length, 3, 'all the cells were reconstructed from JSON');
// Check that the link is before the last cell in the DOM. This check is there because
// paper might have resorted the cells so that links are always AFTER elements.
linkView = this.paper.findViewByModel('b4289c08-07ea-49d2-8dde-e67eb2f2a06a');
rectView = this.paper.findViewByModel('cbd1109e-4d34-4023-91b0-f31bce1318e6');
circleView = this.paper.findViewByModel('bbb9e641-9756-4f42-997a-f4818b89f374');
assert.ok(rectView.el.previousSibling === linkView.el, 'link view is before rect element in the DOM');
assert.ok(linkView.el.previousSibling === circleView.el, 'link view is after circle element in the DOM');
});
QUnit.test('contextmenu', function(assert) {
var r1 = new joint.shapes.basic.Rect({ position: { x: 50, y: 50 }, size: { width: 20, height: 20 }});
this.graph.resetCells([r1]);
var cellContextmenuCallback = sinon.spy();
this.paper.on('cell:contextmenu', cellContextmenuCallback);
var blankContextmenuCallback = sinon.spy();
this.paper.on('blank:contextmenu', blankContextmenuCallback);
var r1View = this.paper.findViewByModel(r1);
r1View.$el.trigger('contextmenu');
assert.ok(cellContextmenuCallback.called, 'cell:contextmenu triggered');
this.paper.$el.trigger('contextmenu');
assert.ok(blankContextmenuCallback.called, 'blank:contextmenu triggered');
});
QUnit.test('paper.getArea()', function(assert) {
this.paper.setOrigin(0, 0);
this.paper.setDimensions(1000, 800);
assert.ok(this.paper.getArea() instanceof g.rect, 'Paper area is a geometry rectangle.');
assert.deepEqual(
_.pick(this.paper.getArea(), 'x', 'y', 'width', 'height'),
{ x: 0, y: 0, width: 1000, height: 800 },
'Paper area returns correct results for unscaled, untranslated viewport.');
this.paper.setOrigin(100, 100);
assert.deepEqual(
_.pick(this.paper.getArea(), 'x', 'y', 'width', 'height'),
{ x: -100, y: -100, width: 1000, height: 800 },
'Paper area returns correct results for unscaled, but translated viewport.');
this.paper.scale(2, 2);
assert.deepEqual(
_.pick(this.paper.getArea(), 'x', 'y', 'width', 'height'),
{ x: -50, y: -50, width: 500, height: 400 },
'Paper area returns correct results for scaled and translated viewport.');
});
QUnit.module('paper.getRestrictedArea()', function() {
QUnit.test('function', function(assert) {
var constraintPoint = function() {};
var spy = sinon.spy(function() { return constraintPoint; });
this.paper.options.restrictTranslate = spy;
assert.equal(this.paper.getRestrictedArea(1,2,3), constraintPoint);
assert.ok(spy.calledWithExactly(1,2,3));
});
QUnit.test('boolean', function(assert) {
this.paper.options.restrictTranslate = true;
assert.ok(this.paper.getRestrictedArea().equals(this.paper.getArea()));
});
QUnit.test('rectangle', function(assert) {
this.paper.options.restrictTranslate = { x: 1, y: 2, width: 3, height: 4 };
assert.ok(this.paper.getRestrictedArea() instanceof g.Rect);
assert.ok(this.paper.getRestrictedArea().equals(new g.Rect(1,2,3,4)));
});
});
QUnit.module('paper.options: restrictTranslate', function() {
QUnit.test('function => function', function(assert) {
var pointSpy = sinon.spy(function() { return new g.Point(); });
var rtSpy = sinon.spy(function() { return pointSpy; });
this.paper.options.gridSize = 1;
this.paper.options.restrictTranslate = rtSpy;
var el = new joint.shapes.standard.Rectangle();
el.resize(100, 100);
el.addTo(this.graph);
var view = el.findView(this.paper);
var data = {};
// down
view.pointerdown({ target: view.el, type: 'mousedown', data: data }, 1, 2);
assert.ok(rtSpy.calledOnce);
assert.ok(rtSpy.calledWithExactly(view, 1, 2));
// move
view.pointermove({ target: view.el, type: 'mousemove', data: data }, 3, 7);
assert.ok(pointSpy.calledOnce);
assert.ok(pointSpy.calledWithExactly(3 - 1, 7 - 2, sinon.match.object));
view.pointermove({ target: view.el, type: 'mousemove', data: data }, 11, 13);
assert.ok(pointSpy.calledTwice);
assert.ok(pointSpy.calledWithExactly(11 - 1, 13 - 2, sinon.match.object));
// up
view.pointerup({ target: view.el, type: 'mouseup', data: data }, 11, 13);
assert.ok(rtSpy.calledOnce);
assert.ok(pointSpy.calledTwice);
});
});
QUnit.test('paper.options: linkView & elementView', function(assert) {
assert.expect(8);
var customElementView = joint.dia.ElementView.extend({ custom: true });
var customLinkView = joint.dia.LinkView.extend({ custom: true });
var element = new joint.shapes.basic.Rect();
var link = new joint.dia.Link();
// Custom View via class
this.paper.options.elementView = customElementView;
this.paper.options.linkView = customLinkView;
this.graph.addCell(element);
assert.equal(element.findView(this.paper).constructor, customElementView,
'custom element view used when "elementView" option contains one.');
this.graph.addCell(link);
assert.equal(link.findView(this.paper).constructor, customLinkView,
'custom link view used when "linkView" option contains one.');
// Custom View via function
element.remove();
link.remove();
this.paper.options.elementView = function(el) {
assert.ok(el === element,
'"elementView" option function executed with correct parameters.');
return customElementView;
};
this.paper.options.linkView = function(l) {
assert.ok(l === link,
'"linkView" option function executed with correct parameters.');
return customLinkView;
};
this.graph.addCell(element);
assert.equal(element.findView(this.paper).constructor, customElementView,
'the custom element view was used when "elementView" option function returns one.');
this.graph.addCell(link);
assert.equal(link.findView(this.paper).constructor, customLinkView,
'the custom link view was used when "linkView" option function returns one.');
// Default View via function
element.remove();
link.remove();
this.paper.options.elementView = function(el) {
return null;
};
this.paper.options.linkView = function(l) {
return null;
};
this.graph.addCell(element);
assert.equal(element.findView(this.paper).constructor, joint.dia.ElementView,
'the default element view was used when "elementView" option function returns no view.');
this.graph.addCell(link);
assert.equal(link.findView(this.paper).constructor, joint.dia.LinkView,
'the default link view was used when "linkView" option function returns no view.');
});
QUnit.test('paper.options: cellViewNamespace', function(assert) {
var customElementView = joint.dia.ElementView.extend({ custom: true });
var customLinkView = joint.dia.LinkView.extend({ custom: true });
var element = new joint.shapes.basic.Rect({ type: 'elements.Element' });
var link = new joint.dia.Link({ type: 'links.Link' });
this.paper.options.cellViewNamespace = {
elements: { ElementView: customElementView },
links: { LinkView: customLinkView }
};
this.graph.addCells([element, link]);
assert.equal(element.findView(this.paper).constructor, customElementView,
'the custom element view was found in the custom namespace.');
assert.equal(link.findView(this.paper).constructor, customLinkView,
'the custom link view was found in the custom namespace.');
});
QUnit.module('connect/disconnect event', function(hooks) {
var connectedLinkView;
var soloLinkView;
var disconnectSpy;
var connectSpy;
var graphCells = [];
hooks.beforeEach(function() {
var source = new joint.shapes.basic.Rect({
id: 'source',
position: { x: 100, y: 100 },
size: { width: 100, height: 100 }
});
var target = new joint.shapes.basic.Rect({
id: 'target',
position: { x: 400, y: 100 },
size: { width: 100, height: 100 }
});
var solo = new joint.shapes.basic.Rect({
id: 'solo',
position: { x: 400, y: 400 },
size: { width: 100, height: 100 }
});
var link = new joint.dia.Link({ id: 'link', source: { id: source.id }, target: { id: target.id }});
var soloLink = new joint.dia.Link({ id: 'link2', source: { id: source.id }, target: { x: 300, y: 300 }});
graphCells = [source, target, solo, link, soloLink];
this.graph.addCells(graphCells);
connectedLinkView = link.findView(this.paper);
soloLinkView = soloLink.findView(this.paper);
disconnectSpy = sinon.spy();
connectSpy = sinon.spy();
this.paper.on('link:disconnect', disconnectSpy);
this.paper.on('link:connect', connectSpy);
});
QUnit.test('disconnect from element', function(assert) {
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 0, 0);
connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 0, 0);
assert.notOk(connectSpy.called);
assert.ok(disconnectSpy.calledOnce);
});
QUnit.test('disconnect from element, connect to new one', function(assert) {
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var soloView = graphCells[2].findView(this.paper);
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: soloView.el, type: 'mousemove', data: data }, 450, 450);
connectedLinkView.pointerup({ target: soloView.el, type: 'mouseup', data: data }, 450, 450);
assert.ok(connectSpy.calledOnce, 'connect to solo');
assert.ok(disconnectSpy.calledOnce, 'disconnect from source');
});
QUnit.test('disconnect from element, connect to same one - nothing changed', function(assert) {
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var targetView = graphCells[1].findView(this.paper);
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: targetView.el, type: 'mousemove', data: data }, 450, 450);
connectedLinkView.pointerup({ target: targetView.el, type: 'mouseup', data: data }, 450, 150);
assert.notOk(connectSpy.called, 'connect should not be called');
assert.notOk(disconnectSpy.called, 'disconnect should not be called');
});
QUnit.module('snapLinks enabled', function(hooks) {
QUnit.test('events', function(assert) {
this.paper.options.snapLinks = true;
var arrowhead = soloLinkView.el.querySelector('.marker-arrowhead[end=target]');
var targetView = graphCells[1].findView(this.paper);
var soloView = graphCells[2].findView(this.paper);
var data = {};
soloLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
soloLinkView.pointermove({ target: soloView.el, type: 'mousemove', data: data }, 450, 450);
soloLinkView.pointermove({ target: targetView.el, type: 'mousemove', data: data }, 450, 150);
soloLinkView.pointerup({ target: targetView.el, type: 'mouseup', data: data }, 450, 450);
assert.ok(connectSpy.calledOnce, 'connect should be called once');
assert.notOk(disconnectSpy.called, 'disconnect should not be called');
});
QUnit.test('validateConnection', function(assert) {
var validateConnectionSpy = sinon.spy(function() { return true; });
this.paper.options.validateConnection = validateConnectionSpy;
this.paper.options.snapLinks = true;
var arrowhead = soloLinkView.el.querySelector('.marker-arrowhead[end=target]');
var targetView = graphCells[1].findView(this.paper);
var soloView = graphCells[2].findView(this.paper);
var data = {};
soloLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
assert.equal(validateConnectionSpy.callCount, 0);
soloLinkView.pointermove({ target: soloView.el, type: 'mousemove', data: data }, 450, 450);
assert.equal(validateConnectionSpy.callCount, 1);
soloLinkView.pointermove({ target: soloView.el, type: 'mousemove', data: data }, 450, 450);
assert.equal(validateConnectionSpy.callCount, 1);
soloLinkView.pointermove({ target: targetView.el, type: 'mousemove', data: data }, 450, 150);
assert.equal(validateConnectionSpy.callCount, 2);
soloLinkView.pointerup({ target: targetView.el, type: 'mouseup', data: data }, 450, 450);
assert.equal(validateConnectionSpy.callCount, 2);
});
});
QUnit.module('linkPinning', function(hooks) {
QUnit.test('enabled - disconnect link with no new target element', function(assert) {
this.paper.options.linkPinning = true;
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.ok(disconnectSpy.called);
assert.notOk(connectSpy.called);
});
QUnit.test('disabled - disconnect link with no new target element', function(assert) {
this.paper.options.linkPinning = true;
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.ok(disconnectSpy.called);
assert.notOk(connectSpy.called);
});
QUnit.test('disconnect when link pinning disabled', function(assert) {
this.paper.options.linkPinning = false;
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.notOk(disconnectSpy.called, 'message');
assert.notOk(connectSpy.called, 'message');
});
});
QUnit.module('allowLink', function(hooks) {
QUnit.test('sanity', function(assert) {
var allowLinkSpy = sinon.spy();
this.paper.options.allowLink = allowLinkSpy;
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.ok(allowLinkSpy.calledOnce);
assert.ok(allowLinkSpy.calledWith(connectedLinkView, connectedLinkView.paper));
assert.equal(allowLinkSpy.thisValues[0], connectedLinkView.paper);
});
QUnit.test('enabled - disconnect when return false', function(assert) {
this.paper.options.allowLink = function() { return false; };
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.notOk(disconnectSpy.called);
});
QUnit.test('enabled - disconnect when return true', function(assert) {
this.paper.options.allowLink = function() { return true; };
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.ok(disconnectSpy.called);
});
QUnit.test('disconnect when disabled', function(assert) {
this.paper.options.allowLink = null;
var arrowhead = connectedLinkView.el.querySelector('.marker-arrowhead[end=target]');
var data = {};
connectedLinkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
connectedLinkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
connectedLinkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.ok(disconnectSpy.called, 'message');
});
});
});
QUnit.module('link:snap:connect/link:snap:disconnect events ', function(hooks) {
var disconnectSpy;
var connectSpy;
var element;
var link;
hooks.beforeEach(function() {
link = new joint.dia.Link();
element = new joint.shapes.devs.Model({
position: { x: 500, y: 250 },
size: { width: 100, height: 100 },
inPorts: ['in1', 'in2']
});
this.graph.addCells([element, link]);
disconnectSpy = sinon.spy();
connectSpy = sinon.spy();
this.paper.on('link:snap:disconnect', disconnectSpy);
this.paper.on('link:snap:connect', connectSpy);
this.paper.options.snapLinks = true;
});
['source', 'target'].forEach(function(end) {
QUnit.test('snapping ' + end + ' to ports', function(assert) {
var paper = this.paper;
var linkView = link.findView(paper);
var elementView = element.findView(paper);
var arrowhead = linkView.el.querySelector('.marker-arrowhead[end=' + end + ']');
var ports = element.getPortsPositions('in');
var position = element.position();
var in1PortEl = elementView.el.querySelector('.port-body[port="in1"]');
var in2PortEl = elementView.el.querySelector('.port-body[port="in2"]');
var x, y, evt;
var data = {};
linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
// Connect to IN1
x = position.x + ports.in1.x;
y = position.y + ports.in1.y;
evt = { target: paper.svg, type: 'mousemove', data: data };
linkView.pointermove(evt, x, y);
assert.ok(connectSpy.calledOnce);
assert.ok(connectSpy.calledWithExactly(linkView, evt, elementView, in1PortEl, end));
assert.notOk(disconnectSpy.called);
// Disconnect from IN1, Connect to IN2
x = position.x + ports.in2.x;
y = position.y + ports.in2.y;
evt = { target: paper.svg, type: 'mousemove', data: data };
linkView.pointermove(evt, x, y);
assert.ok(connectSpy.calledTwice);
assert.ok(connectSpy.calledWithExactly(linkView, evt, elementView, in2PortEl, end));
assert.ok(disconnectSpy.calledOnce);
assert.ok(disconnectSpy.calledWithExactly(linkView, evt, elementView, in1PortEl, end));
// Disconnect from IN2, Connect to a point
x = 0;
y = 0;
evt = { target: paper.svg, type: 'mousemove', data: data };
linkView.pointermove(evt, x, y);
assert.ok(connectSpy.calledTwice);
assert.ok(disconnectSpy.calledTwice);
assert.ok(disconnectSpy.calledWithExactly(linkView, evt, elementView, in2PortEl, end));
linkView.pointerup({ target: paper.svg, type: 'mouseup', data: data }, 0, 0);
assert.ok(connectSpy.calledTwice);
assert.ok(disconnectSpy.calledTwice);
});
});
});
QUnit.module('connect/disconnect to ports event ', function(hooks) {
var disconnectSpy;
var connectSpy;
hooks.beforeEach(function() {
this.modelWithPorts = new joint.shapes.devs.Model({
position: { x: 500, y: 250 },
size: { width: 100, height: 100 },
inPorts: ['in1', 'in2'],
outPorts: ['out']
});
disconnectSpy = sinon.spy();
connectSpy = sinon.spy();
this.paper.on('link:disconnect', disconnectSpy);
this.paper.on('link:connect', connectSpy);
});
QUnit.test('connect to port', function(assert) {
var link = new joint.dia.Link({ id: 'link' });
this.graph.addCells([this.modelWithPorts, link]);
var linkView = link.findView(this.paper);
var arrowhead = linkView.el.querySelector('.marker-arrowhead[end=source]');
var port = this.paper.findViewByModel(this.modelWithPorts).el.querySelector('.port-body[port="in1"]');
var data = {};
linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
linkView.pointermove({ target: port, type: 'mousemove', data: data }, 0, 0);
linkView.pointerup({ target: port, type: 'mouseup', data: data }, 0, 0);
assert.ok(connectSpy.calledOnce);
assert.notOk(disconnectSpy.called);
});
QUnit.test('reconnect port', function(assert) {
var link = new joint.dia.Link({ id: 'link', source: { id: this.modelWithPorts, port: 'in1' }});
this.graph.addCells([this.modelWithPorts, link]);
var linkView = link.findView(this.paper);
var arrowhead = linkView.el.querySelector('.marker-arrowhead[end=source]');
var portElement = this.paper.findViewByModel(this.modelWithPorts).el.querySelector('.port-body[port="in2"]');
var data = {};
linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
linkView.pointermove({ target: portElement, type: 'mousemove', data: data }, 0, 0);
linkView.pointerup({ target: portElement, type: 'mouseup', data: data }, 0, 0);
assert.ok(connectSpy.calledOnce);
assert.ok(disconnectSpy.calledOnce);
});
});
QUnit.test('paper.options: moveThreshold', function(assert) {
var graph = this.graph;
var paper = this.paper;
var el = (new joint.shapes.basic.Rect()).size(100, 100).position(0, 0).addTo(graph);
var elView = el.findView(paper);
var elRect = elView.el.querySelector('rect');
var spy = sinon.spy();
paper.options.moveThreshold = 2;
paper.on('element:pointermove', spy);
var data = {};
paper.pointerdown($.Event('mousedown', { target: elRect, data: data }));
paper.pointermove($.Event('mousemove', { target: elRect, data: data })); // Ignored
paper.pointermove($.Event('mousemove', { target: elRect, data: data })); // Ignored
paper.pointermove($.Event('mousemove', { target: elRect, data: data })); // Processed
paper.pointerup($.Event('mouseup', { target: elRect, data: data }));
assert.ok(spy.calledOnce);
});
QUnit.module('paper.options: magnetThreshold', function(hooks) {
var el, elView, elRect;
hooks.beforeEach(function() {
var graph = this.graph;
var paper = this.paper;
el = new joint.shapes.standard.Rectangle({ attrs: { body: { magnet: true }}});
el.size(100, 100).position(0, 0).addTo(graph);
elView = el.findView(paper);
elRect = elView.el.querySelector('rect');
});
QUnit.test('magnetThreshold: number (0)', function(assert) {
var graph = this.graph;
var paper = this.paper;
var data;
paper.options.magnetThreshold = 0;
data = {};
paper.onmagnet($.Event('mousedown', { currentTarget: elRect, data: data }));
assert.equal(graph.getLinks().length, 1);
paper.pointerup($.Event('mouseup', { target: elRect, data: data }));
});
QUnit.test('magnetThreshold: number (1+)', function(assert) {
var graph = this.graph;
var paper = this.paper;
var data;
paper.options.magnetThreshold = 2;
data = {};
paper.onmagnet($.Event('mousedown', { currentTarget: elRect, data: data }));
paper.pointermove($.Event('mousemove', { target: elRect, data: data })); // Ignored
paper.pointermove($.Event('mousemove', { target: elRect, data: data })); // Ignored
assert.equal(graph.getLinks().length, 0);
paper.pointermove($.Event('mousemove', { target: elRect, data: data })); // Processed
assert.equal(graph.getLinks().length, 1);
paper.pointerup($.Event('mouseup', { target: elRect, data: data }));
});
QUnit.test('magnetThreshold: string ("onleave")', function(assert) {
var graph = this.graph;
var paper = this.paper;
var data;
paper.options.magnetThreshold = 'onleave';
data = {};
paper.onmagnet($.Event('mousedown', { currentTarget: elRect, data: data }));
paper.pointermove($.Event('mousemove', { target: elRect, data: data })); // Ignored
paper.pointermove($.Event('mousemove', { target: elRect, data: data })); // Ignored
assert.equal(graph.getLinks().length, 0);
paper.pointermove($.Event('mousemove', { target: paper.svg, data: data })); // Processed
assert.equal(graph.getLinks().length, 1);
paper.pointerup($.Event('mouseup', { target: elRect, data: data }));
});
});
QUnit.test('paper.options: linkPinning', function(assert) {
assert.expect(5);
var data;
var source = new joint.shapes.basic.Rect({
id: 'source',
position: { x: 100, y: 100 },
size: { width: 100, height: 100 }
});
var target = new joint.shapes.basic.Rect({
id: 'target',
position: { x: 400, y: 100 },
size: { width: 100, height: 100 }
});
var link = new joint.dia.Link({ id: 'link', source: { id: source.id }, target: { id: target.id }});
var newLink; // to be created.
this.graph.addCells([source, target, link]);
var linkView = link.findView(this.paper);
var sourceView = source.findView(this.paper);
var targetView = target.findView(this.paper);
var arrowhead = linkView.el.querySelector('.marker-arrowhead[end=target]');
this.paper.options.linkPinning = false;
data = {};
linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
linkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
linkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.deepEqual(link.get('target'), { id: target.id }, 'pinning disabled: when the arrowhead is dragged&dropped to the blank paper area, the arrowhead is return to its original position.');
this.paper.options.linkPinning = true;
data = {};
linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
linkView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 50, 50);
linkView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 50, 50);
assert.deepEqual(link.get('target'), {
x: 50,
y: 50
}, 'pinning enabled: when the arrowhead is dragged&dropped to the blank paper area, the arrowhead is set to a point.');
this.paper.options.linkPinning = false;
data = {};
linkView.pointerdown({ target: arrowhead, type: 'mousedown', data: data }, 0, 0);
linkView.pointermove({ target: targetView.el, type: 'mousemove', data: data }, 450, 150);
linkView.pointerup({ target: targetView.el, type: 'mouseup', data: data }, 450, 150);
assert.deepEqual(link.get('target'), { id: 'target' }, 'pinning disabled: it\'s still possible to connect link to elements.');
this.paper.options.linkPinning = true;
source.attr('.', { magnet: true });
data = {};
sourceView.dragMagnetStart({ currentTarget: sourceView.el, target: sourceView.el, type: 'mousedown', data: data, stopPropagation: function() {} }, 150, 150);
sourceView.pointermove({ type: 'mousemove', data: data }, 150, 400);
newLink = _.reject(this.graph.getLinks(), { id: 'link' })[0];
if (newLink) {
assert.deepEqual(newLink.get('target'), {
x: 150,
y: 400
}, 'pinning enabled: when there was a link created from a magnet a dropped into the blank paper area, the link target is set to a point.');
newLink.remove();
}
this.paper.options.linkPinning = false;
data = {};
sourceView.pointerdown({ target: sourceView.el, type: 'mousedown', data: data }, 150, 150);
sourceView.pointermove({ target: this.paper.el, type: 'mousemove', data: data }, 150, 400);
sourceView.pointerup({ target: this.paper.el, type: 'mouseup', data: data }, 150, 400);
newLink = _.reject(this.graph.getLinks(), { id: 'link' })[0];
assert.notOk(newLink, 'pinning disabled: when there was a link created from a magnet a dropped into the blank paper area, the link was removed after the drop.');
});
QUnit.test('paper.options: guard', function(assert) {
assert.expect(4);
var element = new joint.shapes.basic.Rect({
position: { x: 100, y: 100 },
size: { width: 100, height: 100 }
});
this.graph.addCell(element);
var elementView = this.paper.findViewByModel(element);
var paperOffsetX = this.paper.$el.offset().left;
var paperOffsetY = this.paper.$el.offset().top;
var bboxBefore = element.getBBox();
var bboxAfter;
var diffX;
var diffY;
simulate.mousedown({
el: elementView.el,
clientX: paperOffsetX + bboxBefore.x + 10,
clientY: paperOffsetY + bboxBefore.y + 10,
button: 1
});
simulate.mousemove({
el: elementView.el,
clientX: paperOffsetX + bboxBefore.x + 50,
clientY: paperOffsetY + bboxBefore.y + 50,
button: 1
});
simulate.mouseup({
el: elementView.el,
clientX: paperOffsetX + bboxBefore.x + 50,
clientY: paperOffsetY + bboxBefore.y + 50,
button: 1
});
bboxAfter = element.getBBox();
diffX = Math.abs(bboxAfter.x - bboxBefore.x);
diffY = Math.abs(bboxAfter.y - bboxBefore.y);
assert.ok(diffX > 30 && diffY > 30, 'element should have been moved');
// Use guard option to only allow mouse events for left mouse button.
this.paper.options.guard = function(evt, view) {
assert.ok(evt instanceof $.Event);
assert.equal(view, elementView);
var isMouseEvent = evt.type.substr(0, 'mouse'.length) === 'mouse';
if (isMouseEvent && evt.button !== 0) {
return true;
}
return false;
};
simulate.mousedown({
el: elementView.el,
clientX: paperOffsetX + bboxBefore.x + 10,
clientY: paperOffsetY + bboxBefore.y + 10,
button: 1
});
simulate.mousemove({
el: elementView.el,
clientX: paperOffsetX + bboxBefore.x + 50,
clientY: paperOffsetY + bboxBefore.y + 50,
button: 1
});
simulate.mouseup({
el: elementView.el,
clientX: paperOffsetX + bboxBefore.x + 50,
clientY: paperOffsetY + bboxBefore.y + 50,
button: 1
});
bboxBefore = bboxAfter;
bboxAfter = element.getBBox();
diffX = Math.abs(bboxAfter.x - bboxBefore.x);
diffY = Math.abs(bboxAfter.y - bboxBefore.y);
assert.ok(diffX < 5 && diffY < 5, 'element should not have been moved');
});
QUnit.test('getContentArea()', function(assert) {
assert.checkBboxApproximately(2/* +- */, this.paper.getContentArea(), {
x: 0,
y: 0,
width: 0,
height: 0
}, 'empty graph, content area should be correct');
var rect1 = new joint.shapes.basic.Rect({
position: {
x: 20,
y: 20
},
size: {
width: 40,
height: 40
}
});
this.graph.addCell(rect1);
assert.checkBboxApproximately(2/* +- */, this.paper.getContentArea(), {
x: 20,
y: 20,
width: 40,
height: 40
}, 'one rectangle, content area should be correct');
var rect2 = new joint.shapes.basic.Rect({
position: {
x: 5,
y: 8
},
size: {
width: 25,
height: 25
}
});
this.graph.addCell(rect2);
assert.checkBboxApproximately(2/* +- */, this.paper.getContentArea(), {
x: 5,
y: 8,
width: 55,
height: 52
}, 'two rectangles, content area should be correct');
var circle1 = new joint.shapes.basic.Circle({
position: {
x: 75,
y: 5
},
size: {
width: 25,
height: 25
}
});
this.graph.addCell(circle1);
assert.checkBboxApproximately(2/* +- */, this.paper.getContentArea(), {
x: 5,
y: 5,
width: 95,
height: 55
}, 'two rectangles + one circle, content area should be correct');
this.paper.scale(2, 2);
assert.checkBboxApproximately(4/* +- */, this.paper.getContentArea(), {
x: 5,
y: 5,
width: 95,
height: 55
}, 'two rectangles + one circle (scaled by factor of 2), content area should be correct');
});
QUnit.test('getContentBBox()', function(assert) {
assert.checkBboxApproximately(2/* +- */, this.paper.getContentBBox(), {
x: 0,
y: 0,
width: 0,
height: 0
}, 'empty graph, content bbox should be correct');
var rect1 = new joint.shapes.basic.Rect({
position: {
x: 20,
y: 20
},
size: {
width: 40,
height: 40
}
});
this.graph.addCell(rect1);
assert.checkBboxApproximately(2/* +- */, this.paper.getContentBBox(), {
x: 20,
y: 20,
width: 40,
height: 40
}, 'one rectangle, content bbox should be correct');
var rect2 = new joint.shapes.basic.Rect({
position: {
x: 5,
y: 8
},
size: {
width: 25,
height: 25
}
});
this.graph.addCell(rect2);
assert.checkBboxApproximately(2/* +- */, this.paper.getContentBBox(), {
x: 5,
y: 8,
width: 55,
height: 52
}, 'two rectangles, content bbox should be correct');
var circle1 = new joint.shapes.basic.Circle({
position: {
x: 75,
y: 5
},
size: {
width: 25,
height: 25
}
});
this.graph.addCell(circle1);
assert.checkBboxApproximately(2/* +- */, this.paper.getContentBBox(), {
x: 5,
y: 5,
width: 95,
height: 55
}, 'two rectangles + one circle, content bbox should be correct');
this.paper.scale(2, 2);
assert.checkBboxApproximately(4/* +- */, this.paper.getContentBBox(), {
x: 10,
y: 10,
width: 190,
height: 110
}, 'two rectangles + one circle (scaled by factor of 2), content bbox should be correct');
});
QUnit.test('findViewsInArea(rect[, opt])', function(assert) {
var cells = [
new joint.shapes.basic.Rect({
position: { x: 20, y: 20 },
size: { width: 20, height: 20 }
}),
new joint.shapes.basic.Rect({
position: { x: 80, y: 80 },
size: { width: 40, height: 60 }
}),
new joint.shapes.basic.Rect({
position: { x: 120, y: 180 },
size: { width: 40, height: 40 }
})
];
this.graph.addCells(cells);
var viewsInArea;
viewsInArea = this.paper.findViewsInArea(new g.rect(0, 0, 10, 10));
assert.equal(viewsInArea.length, 0, 'area with no elements in it');
viewsInArea = this.paper.findViewsInArea(new g.rect(0, 0, 25, 25));
assert.equal(viewsInArea.length, 1, 'area with 1 element in it');
viewsInArea = this.paper.findViewsInArea(new g.rect(0, 0, 300, 300));
assert.equal(viewsInArea.length, 3, 'area with 3 elements in it');
viewsInArea = this.paper.findViewsInArea(new g.rect(0, 0, 100, 100), { strict: true });
assert.equal(viewsInArea.length, 1, '[opt.strict = TRUE] should require elements to be completely within rect');
});
QUnit.test('linkAllowed(linkViewOrModel)', function(assert) {
assert.equal(typeof this.paper.linkAllowed, 'function', 'should be a function');
var paper = this.paper;
assert.throws(function() {
paper.linkAllowed();
}, new Error('Must provide a linkView.'), 'should throw error when linkview is missing');
var rect1 = new joint.shapes.basic.Rect({
position: { x: 20, y: 30 },
size: { width: 40, height: 40 }
});
var rect2 = new joint.shapes.basic.Rect({
position: { x: 80, y: 30 },
size: { width: 40, height: 40 }
});
this.graph.addCells([rect1, rect2]);
// Defaults.
this.paper.options.multiLinks = true;
this.paper.options.linkPinning = true;
var link = new joint.dia.Link({
source: { x: 300, y: 300 },
target: { x: 320, y: 320 }
});
this.graph.addCells([link]);
var linkView = this.paper.findViewByModel(link);
assert.ok(this.paper.linkAllowed(linkView), 'can use link view');
var pinnedLink = new joint.dia.Link({
source: { id: rect1.id },
target: { x: 200, y: 200 }
});
this.graph.addCell(pinnedLink);
var pinnedLinkView = this.paper.findViewByModel(pinnedLink);
this.paper.options.linkPinning = false;
assert.notOk(this.paper.linkAllowed(pinnedLinkView), 'pinned link not allowed when link pinning is disabled');
this.paper.options.linkPinning = true;
assert.ok(this.paper.linkAllowed(pinnedLinkView), 'pinned link allowed when link pinning is enabled');
var multiLink1 = new joint.dia.Link({
source: { id: rect1.id },
target: { id: rect2.id }
});
var multiLink2 = new joint.dia.Link({
source: { id: rect1.id },
target: { id: rect2.id }
});
this.graph.addCells([multiLink1, multiLink2]);
var multiLink2View = this.paper.findViewByModel(