UNPKG

jointjs

Version:

JavaScript diagramming library

1,268 lines (958 loc) 98.6 kB
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(