diff --git a/lib/features/dragging/index.js b/lib/features/dragging/index.js index aad5ab41f..da3dff25f 100644 --- a/lib/features/dragging/index.js +++ b/lib/features/dragging/index.js @@ -1,15 +1,13 @@ +import HoverFixModule from '../hover-fix'; import SelectionModule from '../selection'; import Dragging from './Dragging'; -import HoverFix from './HoverFix'; + export default { - __init__: [ - 'hoverFix' - ], __depends__: [ - SelectionModule + HoverFixModule, + SelectionModule, ], dragging: [ 'type', Dragging ], - hoverFix: [ 'type', HoverFix ] }; \ No newline at end of file diff --git a/lib/features/dragging/HoverFix.js b/lib/features/hover-fix/HoverFix.js similarity index 57% rename from lib/features/dragging/HoverFix.js rename to lib/features/hover-fix/HoverFix.js index e644ab483..41e3dafe0 100644 --- a/lib/features/dragging/HoverFix.js +++ b/lib/features/hover-fix/HoverFix.js @@ -20,14 +20,16 @@ var HIGH_PRIORITY = 1500; * 1) have a hover state after a successful drag.move event * 2) have an out event when dragging leaves an element * - * @param {EventBus} eventBus - * @param {Dragging} dragging * @param {ElementRegistry} elementRegistry + * @param {EventBus} eventBus + * @param {Injector} injector */ -export default function HoverFix(eventBus, dragging, elementRegistry) { +export default function HoverFix(elementRegistry, eventBus, injector) { var self = this; + var dragging = injector.get('dragging', false); + /** * Make sure we are god damn hovering! * @@ -58,80 +60,73 @@ export default function HoverFix(eventBus, dragging, elementRegistry) { } } - /** - * We wait for a specific sequence of events before - * emitting a fake drag.hover event. - * - * Event Sequence: - * - * drag.start - * drag.move >> ensure we are hovering - */ - eventBus.on('drag.start', function(event) { - eventBus.once('drag.move', HIGH_PRIORITY, function(event) { + if (dragging) { - ensureHover(event); + /** + * We wait for a specific sequence of events before + * emitting a fake drag.hover event. + * + * Event Sequence: + * + * drag.start + * drag.move >> ensure we are hovering + */ + eventBus.on('drag.start', function(event) { - }); + eventBus.once('drag.move', HIGH_PRIORITY, function(event) { - }); + ensureHover(event); + + }); + + }); + } /** - * We make sure that drag.out is always fired, even if the + * We make sure that element.out is always fired, even if the * browser swallows an element.out event. * * Event sequence: * - * drag.hover + * element.hover * (element.out >> sometimes swallowed) - * element.hover >> ensure we fired drag.out + * element.hover >> ensure we fired element.out */ - eventBus.on('drag.init', function() { + (function() { + var hoverGfx; + var hover; - var hover, hoverGfx; + eventBus.on('element.hover', function(event) { - function setDragHover(event) { - hover = event.hover; - hoverGfx = event.hoverGfx; - } + // (1) remember current hover element + hoverGfx = event.gfx; + hover = event.element; + }); - function unsetHover() { - hover = null; - hoverGfx = null; - } + eventBus.on('element.hover', HIGH_PRIORITY, function(event) { - function ensureOut() { + // (3) am I on an element still? + if (hover) { - if (!hover) { - return; + // (4) that is a problem, gotta "simulate the out" + eventBus.fire('element.out', { + element: hover, + gfx: hoverGfx + }); } - var element = hover, - gfx = hoverGfx; - - hover = null; - hoverGfx = null; - - // emit synthetic out event - dragging.out({ - element: element, - gfx: gfx - }); - } + }); - eventBus.on('drag.hover', setDragHover); - eventBus.on('element.out', unsetHover); - eventBus.on('element.hover', HIGH_PRIORITY, ensureOut); + eventBus.on('element.out', function() { - eventBus.once('drag.cleanup', function() { - eventBus.off('drag.hover', setDragHover); - eventBus.off('element.out', unsetHover); - eventBus.off('element.hover', ensureOut); + // (2) unset hover state if we correctly outed us *GG* + hoverGfx = null; + hover = null; }); - }); + })(); this._findTargetGfx = function(event) { var position, @@ -152,9 +147,9 @@ export default function HoverFix(eventBus, dragging, elementRegistry) { } HoverFix.$inject = [ + 'elementRegistry', 'eventBus', - 'dragging', - 'elementRegistry' + 'injector' ]; diff --git a/lib/features/hover-fix/index.js b/lib/features/hover-fix/index.js new file mode 100644 index 000000000..4ae4b68a6 --- /dev/null +++ b/lib/features/hover-fix/index.js @@ -0,0 +1,8 @@ +import HoverFix from './HoverFix'; + +export default { + __init__: [ + 'hoverFix' + ], + hoverFix: [ 'type', HoverFix ], +}; \ No newline at end of file diff --git a/test/spec/features/dragging/HoverFixSpec.js b/test/spec/features/dragging/HoverFixSpec.js deleted file mode 100644 index b84c34fa5..000000000 --- a/test/spec/features/dragging/HoverFixSpec.js +++ /dev/null @@ -1,203 +0,0 @@ -import { - bootstrapDiagram, - inject -} from 'test/TestHelper'; - -import { assign } from 'min-dash'; - -import { createCanvasEvent as canvasEvent } from '../../../util/MockEvents'; - -import dragModule from 'lib/features/dragging'; - - -describe('features/dragging - HoverFix', function() { - - beforeEach(bootstrapDiagram({ modules: [ dragModule ] })); - - var shape1, - shape2; - - beforeEach(inject(function(canvas, elementFactory) { - - shape1 = elementFactory.createShape({ - id: 'shape1', - x: 10, - y: 10, - width: 50, - height: 50 - }); - - canvas.addShape(shape1); - - shape2 = elementFactory.createShape({ - id: 'shape2', - x: 100, - y: 10, - width: 50, - height: 50 - }); - - canvas.addShape(shape2); - - })); - - - describe('hover fix', function() { - - beforeEach(inject(function(dragging) { - dragging.setOptions({ manual: true }); - })); - - - it('should ensure hover', inject( - function(eventBus, dragging, hoverFix, elementRegistry) { - - // given - var gfx = elementRegistry.getGraphics(shape1); - - var listener = sinon.spy(function(event) { - expect(event.hover).to.eql(shape1); - expect(event.hoverGfx).to.eql(gfx); - }); - - eventBus.on('drag.hover', listener); - - hoverFix._findTargetGfx = function(event) { - return gfx; - }; - - // when - dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); - dragging.move(canvasEvent({ x: 30, y: 20 })); - - // then - expect(listener).to.have.been.calledOnce; - } - )); - - - it('should trigger hover and then move', inject( - function(eventBus, dragging, hoverFix, elementRegistry) { - - // given - var shape1_gfx = elementRegistry.getGraphics(shape1); - - var recordedEvents = []; - - eventBus.on([ - 'foo.hover', - 'foo.move' - ], function(event) { - recordedEvents.push([ event.type, event.hover ]); - }); - - hoverFix._findTargetGfx = function(event) { - return shape1_gfx; - }; - - // when - dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); - dragging.move(canvasEvent({ x: 30, y: 20 })); - - // then - // first hover and then fake move - expect(recordedEvents).to.eql([ - [ 'foo.hover', shape1 ], - [ 'foo.move', shape1 ] - ]); - } - )); - - }); - - - describe('out fix', function() { - - it('should ensure out', inject(function(dragging, canvas, eventBus) { - - // given - var listener = sinon.spy(function(event) { - expect(event.hover).to.eql(shape1); - expect(event.hoverGfx).to.eql(canvas.getGraphics(shape1)); - }); - - eventBus.on('drag.out', listener); - - // when - dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); - eventBus.fire('element.hover', { element: shape1, gfx: canvas.getGraphics(shape1) }); - - // (no out) - eventBus.fire('element.hover', { element: shape2, gfx: canvas.getGraphics(shape2) }); - - // then - expect(listener).to.have.been.calledOnce; - })); - - - it('should keep event order', inject(function(dragging, canvas, eventBus) { - - // given - var eventNames = [ - 'drag.hover', - 'drag.out' - ], - events = recordEvents(eventNames, eventBus); - - // when - dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); - eventBus.fire('element.hover', { element: shape1, gfx: canvas.getGraphics(shape1) }); - - // (no out) - eventBus.fire('element.hover', { element: shape2, gfx: canvas.getGraphics(shape2) }); - - // then - expect(events).to.eql([ - { type: 'drag.hover', hover: shape1 }, - { type: 'drag.out', hover: shape1 }, - { type: 'drag.hover', hover: shape2 } - ]); - })); - - - it('should prevent additional out', inject(function(dragging, canvas, eventBus) { - - // given - var listener = sinon.spy(function(event) { - expect(event.hover).to.eql(shape1); - expect(event.hoverGfx).to.eql(canvas.getGraphics(shape1)); - }); - - eventBus.on('drag.out', listener); - - // when - dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); - eventBus.fire('element.hover', { element: shape1, gfx: canvas.getGraphics(shape1) }); - eventBus.fire('element.out', { element: shape1, gfx: canvas.getGraphics(shape1) }); - eventBus.fire('element.hover', { element: shape2, gfx: canvas.getGraphics(shape2) }); - - // then - expect(listener).to.have.been.calledOnce; - })); - - }); - -}); - -// helpers ///////////////////// - -function recordEvents(eventNames, eventBus) { - - var events = []; - - eventNames.forEach(function(eventName) { - eventBus.on(eventName, function(e) { - events.push(assign({}, { - type: e.type, - hover: e.hover - })); - }); - }); - - return events; -} diff --git a/test/spec/features/hover-fix/HoverFixSpec.js b/test/spec/features/hover-fix/HoverFixSpec.js new file mode 100644 index 000000000..efac59365 --- /dev/null +++ b/test/spec/features/hover-fix/HoverFixSpec.js @@ -0,0 +1,295 @@ +import { + bootstrapDiagram, + inject +} from 'test/TestHelper'; + +import { assign } from 'min-dash'; + +import { createCanvasEvent as canvasEvent } from '../../../util/MockEvents'; + +import dragModule from 'lib/features/dragging'; +import hoverFixModule from 'lib/features/hover-fix'; + + +describe('features/hover-fix', function() { + + describe('element-level hover fix', function() { + + beforeEach(bootstrapDiagram({ modules: [ hoverFixModule ] })); + + var shape1, + shape2; + + beforeEach(inject(function(canvas, elementFactory) { + + shape1 = elementFactory.createShape({ + id: 'shape1', + x: 10, + y: 10, + width: 50, + height: 50 + }); + + canvas.addShape(shape1); + + shape2 = elementFactory.createShape({ + id: 'shape2', + x: 100, + y: 10, + width: 50, + height: 50 + }); + + canvas.addShape(shape2); + + })); + + + it('should ensure the correct events are fired in sequence', inject(function(canvas, eventBus) { + + // given + var events = []; + + eventBus.on(['element.hover', 'element.out'], function(event) { + events.push({ type: event.type, element: event.element }); + }); + + eventBus.fire('element.hover', { element: shape1, gfx: canvas.getGraphics(shape1) }); + + // assume + expect(events).to.eql([{ type: 'element.hover', element: shape1 }]); + + // when + eventBus.fire('element.hover', { element: shape2, gfx: canvas.getGraphics(shape2) }); + + // then + expect(events).to.eql([ + { type: 'element.hover', element: shape1 }, + { type: 'element.out', element: shape1 }, + { type: 'element.hover', element: shape2 } + ]); + })); + + + it('should not interfere with the normal hovering process', inject(function(canvas, eventBus) { + + // given + var events = []; + + eventBus.on(['element.hover', 'element.out'], function(event) { + events.push({ type: event.type, element: event.element }); + }); + + eventBus.fire('element.hover', { element: shape1, gfx: canvas.getGraphics(shape1) }); + + // assume + expect(events).to.eql([{ type: 'element.hover', element: shape1 }]); + + // when + eventBus.fire('element.out', { element: shape1, gfx: canvas.getGraphics(shape2) }); + eventBus.fire('element.hover', { element: shape2, gfx: canvas.getGraphics(shape2) }); + + // then + expect(events).to.eql([ + { type: 'element.hover', element: shape1 }, + { type: 'element.out', element: shape1 }, + { type: 'element.hover', element: shape2 } + ]); + })); + + }); + + + describe('dragging-level hover', function() { + + beforeEach(bootstrapDiagram({ modules: [ dragModule ] })); + + var shape1, + shape2; + + beforeEach(inject(function(canvas, elementFactory) { + + shape1 = elementFactory.createShape({ + id: 'shape1', + x: 10, + y: 10, + width: 50, + height: 50 + }); + + canvas.addShape(shape1); + + shape2 = elementFactory.createShape({ + id: 'shape2', + x: 100, + y: 10, + width: 50, + height: 50 + }); + + canvas.addShape(shape2); + + })); + + + describe('in fix', function() { + + beforeEach(inject(function(dragging) { + dragging.setOptions({ manual: true }); + })); + + + it('should ensure hover', inject( + function(eventBus, dragging, hoverFix, elementRegistry) { + + // given + var gfx = elementRegistry.getGraphics(shape1); + + var listener = sinon.spy(function(event) { + expect(event.hover).to.eql(shape1); + expect(event.hoverGfx).to.eql(gfx); + }); + + eventBus.on('drag.hover', listener); + + hoverFix._findTargetGfx = function(event) { + return gfx; + }; + + // when + dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); + dragging.move(canvasEvent({ x: 30, y: 20 })); + + // then + expect(listener).to.have.been.calledOnce; + } + )); + + + it('should trigger hover and then move', inject( + function(eventBus, dragging, hoverFix, elementRegistry) { + + // given + var shape1_gfx = elementRegistry.getGraphics(shape1); + + var recordedEvents = []; + + eventBus.on([ + 'foo.hover', + 'foo.move' + ], function(event) { + recordedEvents.push([ event.type, event.hover ]); + }); + + hoverFix._findTargetGfx = function(event) { + return shape1_gfx; + }; + + // when + dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); + dragging.move(canvasEvent({ x: 30, y: 20 })); + + // then + // first hover and then fake move + expect(recordedEvents).to.eql([ + [ 'foo.hover', shape1 ], + [ 'foo.move', shape1 ] + ]); + } + )); + + }); + + + describe('out fix', function() { + + it('should ensure out', inject(function(dragging, canvas, eventBus) { + + // given + var listener = sinon.spy(function(event) { + expect(event.hover).to.eql(shape1); + expect(event.hoverGfx).to.eql(canvas.getGraphics(shape1)); + }); + + eventBus.on('drag.out', listener); + + // when + dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); + eventBus.fire('element.hover', { element: shape1, gfx: canvas.getGraphics(shape1) }); + + // (no out) + eventBus.fire('element.hover', { element: shape2, gfx: canvas.getGraphics(shape2) }); + + // then + expect(listener).to.have.been.calledOnce; + })); + + + it('should keep event order', inject(function(dragging, canvas, eventBus) { + + // given + var eventNames = [ + 'drag.hover', + 'drag.out' + ], + events = recordEvents(eventNames, eventBus); + + // when + dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); + eventBus.fire('element.hover', { element: shape1, gfx: canvas.getGraphics(shape1) }); + + // (no out) + eventBus.fire('element.hover', { element: shape2, gfx: canvas.getGraphics(shape2) }); + + // then + expect(events).to.eql([ + { type: 'drag.hover', hover: shape1 }, + { type: 'drag.out', hover: shape1 }, + { type: 'drag.hover', hover: shape2 } + ]); + })); + + + it('should prevent additional out', inject(function(dragging, canvas, eventBus) { + + // given + var listener = sinon.spy(function(event) { + expect(event.hover).to.eql(shape1); + expect(event.hoverGfx).to.eql(canvas.getGraphics(shape1)); + }); + + eventBus.on('drag.out', listener); + + // when + dragging.init(canvasEvent({ x: 10, y: 10 }), 'foo'); + eventBus.fire('element.hover', { element: shape1, gfx: canvas.getGraphics(shape1) }); + eventBus.fire('element.out', { element: shape1, gfx: canvas.getGraphics(shape1) }); + eventBus.fire('element.hover', { element: shape2, gfx: canvas.getGraphics(shape2) }); + + // then + expect(listener).to.have.been.calledOnce; + })); + + }); + + }); + +}); + +// helpers ///////////////////// + +function recordEvents(eventNames, eventBus) { + + var events = []; + + eventNames.forEach(function(eventName) { + eventBus.on(eventName, function(e) { + events.push(assign({}, { + type: e.type, + hover: e.hover + })); + }); + }); + + return events; +}