import { bootstrapDiagram, getDiagramJS, inject } from 'test/TestHelper'; import { forEach, assign } from 'min-dash'; import { domify } from 'min-dom'; import overlayModule from 'lib/features/overlays'; describe('features/overlays', function() { describe('bootstrap', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ] })); it('should expose api', inject(function(overlays) { expect(overlays).to.exist; expect(overlays.get).to.exist; expect(overlays.add).to.exist; expect(overlays.remove).to.exist; })); }); describe('#add', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ] })); it('should add
', inject(function(overlays, canvas) { // given var shape = canvas.addShape({ id: 'test', x: 10, y: 10, width: 100, height: 100 }); // when var id = overlays.add(shape, { position: { left: 0, top: 0 }, html: '
' }); // then expect(id).to.exist; expect(overlays.get(id)).to.exist; expect(queryOverlay(id)).to.exist; })); it('should add element overlay', inject(function(overlays, canvas) { // given var shape = canvas.addShape({ id: 'test', x: 10, y: 10, width: 100, height: 100 }); // when var id = overlays.add(shape, { position: { left: 0, top: 0 }, html: highlight(domify('
')) }); // then var overlay = overlays.get(id); expect(overlay).to.exist; expect(isVisible(overlays._overlayRoot)).to.be.true; expect(isVisible(overlay.html)).to.be.true; expect(queryOverlay(id)).to.exist; })); it('should add overlay on shape (by id)', inject(function(overlays, canvas) { // given canvas.addShape({ id: 'test', x: 10, y: 10, width: 100, height: 100 }); // when var id = overlays.add('test', { position: { left: 0, top: 0 }, html: highlight(domify('
')) }); // then expect(overlays.get(id)).to.exist; expect(queryOverlay(id)).to.exist; })); }); describe('#remove', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ] })); it('should remove overlay', inject(function(overlays, canvas) { // given var shape = canvas.addShape({ id: 'shape', x: 10, y: 10, width: 100, height: 100 }); var id = overlays.add(shape, { position: { left: 0, top: 0 }, html: highlight(domify('
')) }); // when overlays.remove(id); // then expect(overlays.get(id)).not.to.exist; expect(overlays.get({ element: shape })).to.be.empty; expect(queryOverlay(id)).not.to.exist; })); it('should remove multiple by filter', inject(function(overlays, canvas) { // given var shape = canvas.addShape({ id: 'shape', x: 10, y: 10, width: 100, height: 100 }); overlays.add(shape, 'badge', { position: { left: 0, top: 0 }, html: highlight(domify('
1
')) }); overlays.add(shape, 'badge', { position: { right: 0, top: 0 }, html: highlight(domify('
2
')) }); // when overlays.remove({ element: shape, type: 'badge' }); // then expect(overlays.get({ element: shape, type: 'badge' })).to.be.empty; expect(overlays.get({ element: shape })).to.be.empty; expect(overlays._getOverlayContainer(shape, true).html.textContent).to.equal(''); })); it('should remove automatically on <*.remove>', inject(function(eventBus, overlays, canvas) { // given var shape = canvas.addShape({ id: 'test', x: 10, y: 10, width: 100, height: 100 }); var id = overlays.add(shape, { position: { left: 0, top: 0 }, html: '
' }); // when eventBus.fire('shape.remove', { element: shape }); // then expect(overlays.get(id)).not.to.exist; expect(queryOverlay(id)).not.to.exist; expect(overlays._overlayContainers).to.be.empty; })); it('should remove cached container on <*.remove>', inject(function(eventBus, overlays, canvas) { // given var shape1 = canvas.addShape({ id: 'test1', x: 10, y: 10, width: 100, height: 100 }); overlays.add(shape1, { position: { left: 0, top: 0 }, html: '
' }); var shape2 = canvas.addShape({ id: 'test2', x: 150, y: 10, width: 100, height: 100 }); overlays.add(shape2, { position: { left: 0, top: 0 }, html: '
' }); var shape3 = canvas.addShape({ id: 'test3', x: 300, y: 10, width: 100, height: 100 }); overlays.add(shape3, { position: { left: 0, top: 0 }, html: '
' }); // when eventBus.fire('shape.remove', { element: shape2 }); // then expect(overlays._getOverlayContainer(shape1, true)).to.exist; expect(overlays._getOverlayContainer(shape2, true)).not.to.exist; expect(overlays._getOverlayContainer(shape3, true)).to.exist; })); it('should reattach overlays on element ID change', inject(function(overlays, canvas, elementRegistry) { // given var oldId = 'old', newId = 'new'; var shape = canvas.addShape({ id: oldId, x: 10, y: 10, width: 100, height: 100 }); var id = overlays.add(shape, { position: { left: 0, top: 0 }, html: '
' }); var overlay = overlays.get(id); // when elementRegistry.updateId(shape, newId); // then expect(overlays.get(id)).to.eql(overlay); expect(queryOverlay(id)).to.exist; expect(overlays.get({ element: shape })).to.eql([ overlay ]); })); }); describe('#get', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ] })); var shape1, shape2, overlay1, overlay2, overlay3; beforeEach(inject(function(canvas, overlays) { shape1 = canvas.addShape({ id: 'shape1', x: 100, y: 100, width: 100, height: 100 }); shape2 = canvas.addShape({ id: 'shape2', x: 300, y: 100, width: 50, height: 50 }); overlay1 = { position: { top: 10, left: 20 }, html: createOverlay() }; overlay1.id = overlays.add(shape1, 'badge', overlay1); overlay2 = { position: { bottom: 50, right: 0 }, html: createOverlay() }; overlay2.id = overlays.add(shape1, overlay2); overlay3 = { position: { top: 10, left: 20 }, html: createOverlay() }; overlay3.id = overlays.add(shape2, 'badge', overlay3); })); it('should return overlays by element', inject(function(overlays) { // when var filteredOverlays = overlays.get({ element: shape1 }); // then expect(filteredOverlays.length).to.equal(2); })); it('should return overlays by element + type', inject(function(overlays) { // when var filteredOverlays = overlays.get({ element: shape2, type: 'badge' }); // then expect(filteredOverlays.length).to.equal(1); })); it('should return overlays by type', inject(function(overlays) { // when var filteredOverlays = overlays.get({ type: 'badge' }); // then expect(filteredOverlays.length).to.equal(2); })); }); describe('positioning', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ] })); var shape; beforeEach(inject(function(canvas) { shape = canvas.addShape({ id: 'shape', x: 100, y: 100, width: 100, height: 100 }); })); function position(overlayHtml) { var parent = overlayHtml.parentNode; var result = {}; forEach([ 'left', 'right', 'top', 'bottom' ], function(pos) { var p = parseInt(parent.style[pos]); if (!isNaN(p)) { result[pos] = p; } }); return result; } it('should position top left of shape', inject(function(overlays) { var html = createOverlay(); // when overlays.add(shape, { position: { left: 40, top: 40 }, html: html }); // then expect(position(html)).to.eql({ top: 40, left: 40 }); })); it('should position bottom left of shape', inject(function(overlays) { var html = createOverlay(); // when overlays.add(shape, { position: { bottom: 40, left: 40 }, html: html }); // then expect(position(html)).to.eql({ top: 60, left: 40 }); })); it('should position top left of shape', inject(function(overlays, canvas) { var html = createOverlay(); var connection = canvas.addConnection({ id: 'select1', waypoints: [ { x: 10, y: 10 }, { x: 110, y: 10 } ] }); // when overlays.add(connection, { position: { left: 100, top: 0 }, html: html }); // then expect(position(html)).to.eql({ top: 0, left: 100 }); })); it('should position bottom right of shape', inject(function(overlays) { var html = createOverlay(); // when overlays.add(shape, { position: { bottom: 40, right: 40 }, html: html }); // then expect(position(html)).to.eql({ top: 60, left: 60 }); })); it('should position top right of shape', inject(function(overlays) { var html = createOverlay(); // when overlays.add(shape, { position: { top: 40, right: 40 }, html: html }); // then expect(position(html)).to.eql({ top: 40, left: 60 }); })); }); describe('show behavior', function() { describe('default', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ], canvas: { deferUpdate: false } })); var shape; beforeEach(inject(function(canvas) { shape = canvas.addShape({ id: 'shape', x: 100, y: 100, width: 100, height: 100 }); })); function isVisible(element) { return element.parentNode.style.display !== 'none'; } it('should always show overlays', inject(function(overlays, canvas) { // given var html = createOverlay(); overlays.add(shape, { html: html, position: { left: 20, bottom: 0 } }); // when zoom in visibility range canvas.zoom(0.7); // then expect(isVisible(html)).to.be.true; // when zoom below visibility range canvas.zoom(0.6); // then expect(isVisible(html)).to.be.true; // when zoom in visibility range canvas.zoom(3.0); // then expect(isVisible(html)).to.be.true; // when zoom above visibility range canvas.zoom(6.0); // then expect(isVisible(html)).to.be.true; })); }); describe('overriding defaults', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ], canvas: { deferUpdate: false }, overlays: { defaults: { show: { minZoom: 0.7, maxZoom: 5.0 } } } })); var shape; beforeEach(inject(function(canvas) { shape = canvas.addShape({ id: 'shape', x: 100, y: 100, width: 100, height: 100 }); })); function isVisible(element) { return element.parentNode.style.display !== 'none'; } it('should conditionally hide overlays', inject(function(overlays, canvas) { // given var html = createOverlay(); overlays.add(shape, { html: html, position: { left: 20, bottom: 0 } }); // when zoom in visibility range canvas.zoom(0.7); // then expect(isVisible(html)).to.be.true; // when zoom below visibility range canvas.zoom(0.6); // then expect(isVisible(html)).to.be.false; // when zoom in visibility range canvas.zoom(3.0); // then expect(isVisible(html)).to.be.true; // when zoom above visibility range canvas.zoom(6.0); // then expect(isVisible(html)).to.be.false; })); }); }); describe('conditional hide behavior', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ], canvas: { deferUpdate: false }, overlays: { defaults: { show: { minZoom: 0.7, maxZoom: 5.0 } } } })); var shape; beforeEach(inject(function(canvas) { shape = canvas.addShape({ id: 'shape', x: 100, y: 100, width: 100, height: 100 }); })); function zoom(level) { getDiagramJS().invoke(function(canvas) { canvas.zoom(level); }); } function isVisible(element) { return element.parentNode.style.display !== 'none'; } function expectVisible(el, visible) { expect(isVisible(el)).to.equal(visible); } it('should respect min show rules when overlay is added', inject(function(overlays) { // given var html = createOverlay(); // when zoom below visibility range zoom(0.6); overlays.add(shape, { html: html, position: { left: 20, bottom: 0 } }); // then expectVisible(html, false); // when zoom in visibility range zoom(0.7); // then expectVisible(html, true); })); it('should respect max show rules when overlay is added', inject(function(overlays, canvas) { // given var html = createOverlay(); // when zoom above visibility range zoom(6.0); overlays.add(shape, { html: html, position: { left: 20, bottom: 0 } }); // then expectVisible(html, false); // when zoom in visibility range zoom(3.0); // then expectVisible(html, true); })); it('should respect overlay specific min/max rules', inject(function(overlays, canvas) { // given var html = createOverlay(); overlays.add(shape, { html: html, position: { left: 20, bottom: 0 }, show: { minZoom: 0.7, maxZoom: 1.3 } }); // when // zoom below configured minZoom zoom(0.5); // then expectVisible(html, false); // when // zoom on configured minZoom zoom(0.7); // then expectVisible(html, true); // when // zoom into visibility range zoom(0.9); // then expectVisible(html, true); // when // zoom on configured maxZoom zoom(1.3); // then expectVisible(html, true); // when // zoom above maxZoom zoom(1.4); // then expectVisible(html, false); })); it('should respect overlay specific min rule', inject(function(overlays, canvas) { // given var html = createOverlay(); overlays.add(shape, { html: html, position: { left: 20, bottom: 0 }, show: { minZoom: 0.7 } }); // when // zoom below configured minZoom zoom(0.5); // then expectVisible(html, false); // when zoom on configured minZoom zoom(0.7); // then expectVisible(html, true); })); it('should respect overlay specific max rule', inject(function(overlays, canvas) { // given var html = createOverlay(); overlays.add(shape, { html: html, position: { left: 20, bottom: 0 }, show: { maxZoom: 1.3 } }); // when // zoom on configured maxZoom zoom(1.3); // then expectVisible(html, true); // when // zoom above maxZoom zoom(1.4); // then expectVisible(html, false); })); }); describe('scroll/zoom behavior', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ], canvas: { deferUpdate: false } })); var shape, overlay; beforeEach(inject(function(canvas, overlays) { shape = canvas.addShape({ id: 'shape', x: 100, y: 100, width: 100, height: 100 }); overlay = { html: createOverlay(), position: { left: 20, top: 20 } }; overlays.add(shape, overlay); })); it('should not be transformed initially', inject(function(overlays, canvas) { // given // diagram got newly created // then expect(transformMatrix(overlays._overlayRoot)).not.to.exist; })); it('should transform overlay container on scroll', inject(function(overlays, canvas) { // when canvas.scroll({ dx: 100, dy: 50 }); var mtrx = transformMatrix(overlays._overlayRoot); // then expect(mtrx).to.eql({ a : 1, b : 0, c : 0, d : 1, e : 100, f : 50 }); })); it('should transform overlay container on zoom', inject(function(overlays, canvas) { // when canvas.zoom(2); var mtrx = transformMatrix(overlays._overlayRoot); // then expect(mtrx.a).to.eql(2); expect(mtrx.d).to.eql(2); })); it('should add css prefixes to the overlay container on zoom', inject(function(overlays, canvas) { // given var containerStyle = overlays._overlayRoot.style; // when canvas.zoom(2); // then expect(containerStyle['-webkit-transform']).to.match(/matrix/); expect(containerStyle['-ms-transform']).to.match(/matrix/); })); it('should transform overlay container on zoom (with position)', inject(function(overlays, canvas) { // when canvas.zoom(2, { x: 300, y: 300 }); // then expect(transformMatrix(overlays._overlayRoot)).to.eql({ a : 2, b : 0, c : 0, d : 2, e : -300, f : -300 }); })); }); describe('overlay scaling behavior', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ], canvas: { deferUpdate: false } })); function scaleVector(element) { return asVector(element.style.transform); } function verifyScale(overlayConfig, expectedScales) { var test = inject(function(canvas, overlays) { // given var shape = canvas.addShape({ id: 'shape', x: 100, y: 100, width: 100, height: 100 }); var overlay = assign({ html: createOverlay(), position: { left: 20, top: 20 } }, overlayConfig); overlays.add(shape, overlay); var overlayParent = overlay.html.parentNode; // test multiple zoom steps [ 1.0, 1.5, 3.5, 10, 0.5 ].forEach(function(zoom, idx) { var expectedScale = expectedScales[idx]; // when canvas.zoom(zoom); var actualScale = scaleVector(overlayParent) || { x: 1, y: 1 }; var effectiveScale = zoom * actualScale.x; // then expect(actualScale.x).to.eql(actualScale.y); expect(effectiveScale).to.be.closeTo(expectedScale, 0.00001); }); }); test(); } it('should scale per default', function() { // given var overlay = { }; var expectedScales = [ 1.0, 1.5, 3.5, 10, 0.5 ]; // expect verifyScale(overlay, expectedScales); }); it('should scale with explicit scale = true', function() { // given var overlay = { scale: true }; var expectedScales = [ 1.0, 1.5, 3.5, 10, 0.5 ]; // expect verifyScale(overlay, expectedScales); }); it('should not scale with scale = false', function() { // given var overlay = { scale: false }; var expectedScales = [ 1.0, 1.0, 1.0, 1.0, 1.0 ]; // expect verifyScale(overlay, expectedScales); }); it('should configure scale with min / max', function() { // given var overlay = { scale: { min: 0.9, max: 1.6 } }; var expectedScales = [ 1.0, 1.5, 1.6, 1.6, 0.9 ]; // expect verifyScale(overlay, expectedScales); }); }); describe('#clear', function() { beforeEach(bootstrapDiagram({ modules: [ overlayModule ] })); it('should add
', inject(function(overlays, eventBus, canvas) { // given var shape = canvas.addShape({ id: 'test', x: 10, y: 10, width: 100, height: 100 }); var id = overlays.add(shape, { position: { left: 0, top: 0 }, html: '
' }); // when eventBus.fire('diagram.clear'); // then expect(overlays.get(id)).not.to.exist; expect(queryOverlay(id)).not.to.exist; })); }); }); // helpers ////////////////////// var NUM_REGEX = /([+-]?\d*[.]?\d+)(?=,|\))/g; var overlaysCounter = 0; function asMatrix(transformStr) { if (transformStr && transformStr !== 'none') { var m = transformStr.match(NUM_REGEX); return { a: parseFloat(m[0], 10), b: parseFloat(m[1], 10), c: parseFloat(m[2], 10), d: parseFloat(m[3], 10), e: parseFloat(m[4], 10), f: parseFloat(m[5], 10) }; } } function asVector(scaleStr) { if (scaleStr && scaleStr !== 'none') { var m = scaleStr.match(NUM_REGEX); var x = parseFloat(m[0], 10); var y = m[1] ? parseFloat(m[1], 10) : x; return { x: x, y: y }; } } function isVisible(element) { return window.getComputedStyle(element).display !== 'none'; } function highlight(element) { assign(element.style, { background: 'fuchsia', minWidth: '10px', minHeight: '10px' }); return element; } function queryOverlay(id) { return document.querySelector('[data-overlay-id=' + id + ']'); } function createOverlay() { var element = highlight(domify('
OV
#' + (overlaysCounter++) + '
')); assign(element.style, { width: 40, height: 40 }); return element; } function transformMatrix(element) { return asMatrix(element.style.transform); }