From 8d0ef135053d2c337e0f43fb0529dff1770587ab Mon Sep 17 00:00:00 2001 From: Andreas Hocevar Date: Sun, 31 Jan 2016 20:33:33 +0100 Subject: [PATCH] Add tests and documentation for goog.events.* --- src/ol/events.js | 73 +++--- test/spec/ol/events.test.js | 208 ++++++++++++++++++ test/spec/ol/pointer/mousesource.test.js | 4 +- .../ol/pointer/pointereventhandler.test.js | 2 +- test/spec/ol/pointer/touchsource.test.js | 2 +- 5 files changed, 258 insertions(+), 31 deletions(-) create mode 100644 test/spec/ol/events.test.js diff --git a/src/ol/events.js b/src/ol/events.js index c17eaba5af..36d088a98f 100644 --- a/src/ol/events.js +++ b/src/ol/events.js @@ -53,23 +53,20 @@ ol.events.KeyCode = { }; -// Event manager inspired by -// https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html - - /** * Property name on an event target for the listener map associated with the * event target. * @const {string} * @private */ -ol.events.LISTENER_MAP_PROP_ = 'olm_' + ((Math.random() * 1e6) | 0); +ol.events.LISTENER_MAP_PROP_ = 'olm_' + ((Math.random() * 1e4) | 0); /** * @typedef {EventTarget|ol.events.EventTarget| * {addEventListener: function(string, Function, boolean=), - * removeEventListener: function(string, Function, boolean=)}} + * removeEventListener: function(string, Function, boolean=), + * dispatchEvent: function((ol.events.EventType|string))}} */ ol.events.EventTargetLike; @@ -106,24 +103,26 @@ ol.events.ListenerObjType; /** - * @param {ol.events.ListenerFunctionType} listener Listener. * @param {ol.events.ListenerObjType} listenerObj Listener object. * @return {ol.events.ListenerFunctionType} Bound listener. */ -ol.events.bindListener_ = function(listener, listenerObj) { - return function(evt) { +ol.events.bindListener_ = function(listenerObj) { + var boundListener = function(evt) { var rv = listenerObj.listener.call(listenerObj.bindTo, evt); if (listenerObj.callOnce) { ol.events.unlistenByKey(listenerObj); } return rv; } + listenerObj.boundListener = boundListener; + return boundListener; }; /** * Finds the matching {@link ol.events.ListenerObjType} in the given listener * array. + * * @param {!Array} listenerArray Array of listeners. * @param {!Function} listener The listener function. * @param {boolean} useCapture The capture flag for the listener. @@ -150,19 +149,6 @@ ol.events.findListener_ = function( }; -/** - * @param {EventTarget|ol.events.EventTarget} target Event target. - * @param {ol.events.EventType|string|Array.<(ol.events.EventType|string)>} type - * Event t@param {Event|ol.events.Event} event Event to dispatch on the - * `target`. - * @param {Event|ol.events.Event} event Event. - */ -ol.events.fireListeners = function(target, type, event) { - event.type = type; - target.dispatchEvent(event); -}; - - /** * @param {ol.events.EventTargetLike} target Target. * @param {ol.events.EventType|string} type Type. @@ -175,6 +161,12 @@ ol.events.getListeners = function(target, type) { /** + * Registers an event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * This function efficiently binds a `listener` to a `this` object, and returns + * a key for use with {@link ol.events.unlistenByKey}. + * * @param {EventTarget|ol.events.EventTarget| * {removeEventListener: function(string, Function, boolean=)}} target * Event target. @@ -199,7 +191,7 @@ ol.events.listen = function( }); return keys; } - goog.asserts.assertString(type); + var useCapture = !!opt_useCapture; var listenerMap = target[ol.events.LISTENER_MAP_PROP_]; if (!listenerMap) { @@ -225,8 +217,8 @@ ol.events.listen = function( type: type, useCapture: useCapture }); - listenerObj.boundListener = ol.events.bindListener_(listener, listenerObj); - target.addEventListener(type, listenerObj.boundListener, useCapture); + target.addEventListener(type, ol.events.bindListener_(listenerObj), + useCapture); listenerArray.push(listenerObj); } @@ -235,6 +227,18 @@ ol.events.listen = function( /** + * Registers a one-off event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * This function efficiently binds a `listener` as self-unregistering listener + * to a `this` object, and returns a key for use with + * {@link ol.events.unlistenByKey} in case the listener needs to be unregistered + * before it is called. + * + * When {@link ol.events.listen} is called with the same arguments after this + * function, the self-unregistering listener will be turned into a permanent + * listener. + * * @param {ol.events.EventTargetLike} target Event target. * @param {ol.events.EventType|string|Array.<(ol.events.EventType|string)>} type * Event type. @@ -254,6 +258,12 @@ ol.events.listenOnce = function( /** + * Unregisters an event listener on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * To return a listener, this function needs to be called with the exact same + * arguments that were used for a previous {@link ol.events.listen} call. + * * @param {ol.events.EventTargetLike} target Event target. * @param {ol.events.EventType|string|Array.<(ol.events.EventType|string)>} type * Event type. @@ -272,7 +282,7 @@ ol.events.unlisten = function( }); return; } - + goog.asserts.assertString(type, 'type is a string'); var listenerArray = ol.events.getListeners(target, type); if (listenerArray) { var listenerObj = ol.events.findListener_(listenerArray, listener, @@ -285,6 +295,12 @@ ol.events.unlisten = function( /** + * Unregisters event listeners on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * The argument passed to this function is the key returned from + * {@link ol.events.listen} or {@link ol.events.listenOnce}. + * * @param {ol.events.Key} key Key or keys. */ ol.events.unlistenByKey = function(key) { @@ -313,7 +329,10 @@ ol.events.unlistenByKey = function(key) { /** - * @param {EventTarget|ol.events.EventTarget} target Target. + * Unregisters all event listeners on an event target. Inspired by + * {@link https://google.github.io/closure-library/api/source/closure/goog/events/events.js.src.html} + * + * @param {ol.events.EventTargetLike} target Target. */ ol.events.unlistenAll = function(target) { var listenerMap = target[ol.events.LISTENER_MAP_PROP_]; diff --git a/test/spec/ol/events.test.js b/test/spec/ol/events.test.js new file mode 100644 index 0000000000..395b00c5b5 --- /dev/null +++ b/test/spec/ol/events.test.js @@ -0,0 +1,208 @@ +goog.provide('ol.test.events'); + + +describe('ol.events', function() { + var add, remove, target; + + beforeEach(function() { + add = sinon.spy(); + remove = sinon.spy(); + target = { + addEventListener: add, + removeEventListener: remove + }; + }); + + describe('bindListener_()', function() { + it('binds a listener and returns a bound listener function', function() { + var listenerObj = { + listener: sinon.spy(), + bindTo: {id: 1} + }; + var boundListener = ol.events.bindListener_(listenerObj); + expect(listenerObj.boundListener).to.equal(boundListener); + boundListener(); + expect(listenerObj.listener.thisValues[0]).to.equal(listenerObj.bindTo); + }); + it('binds a self-unregistering listener when callOnce is true', function() { + var bindTo = {id: 1}; + var listener = sinon.spy(); + target.removeEventListener = function() {}; + var listenerObj = { + type: 'foo', + target: target, + listener: listener, + bindTo: bindTo, + callOnce: true + }; + var boundListener = ol.events.bindListener_(listenerObj); + expect(listenerObj.boundListener).to.equal(boundListener); + var spy = sinon.spy(ol.events, 'unlistenByKey'); + boundListener(); + expect(listener.thisValues[0]).to.equal(bindTo); + expect(spy.firstCall.args[0]).to.eql(listenerObj); + ol.events.unlistenByKey.restore(); + }); + }); + + describe('findListener_()', function() { + it('searches a listener array for a specific listener', function() { + var bindTo = {id: 1}; + var listener = function() {}; + var listenerObj = { + type: 'foo', + target: target, + listener: listener, + useCapture: false + }; + var listenerArray = [listenerObj]; + var result = ol.events.findListener_(listenerArray, listener, false); + expect(result).to.be(listenerObj); + result = ol.events.findListener_(listenerArray, listener, false, bindTo); + expect(result).to.be(undefined); + listenerObj.bindTo = bindTo; + result = ol.events.findListener_(listenerArray, listener, false); + expect(result).to.be(undefined); + result = ol.events.findListener_(listenerArray, listener, false, bindTo); + expect(result).to.be(listenerObj); + }); + }); + + describe('getListeners()', function() { + it('returns listeners for a target and type', function() { + var foo = ol.events.listen(target, 'foo', function() {}); + var bar = ol.events.listen(target, 'bar', function() {}); + expect (ol.events.getListeners(target, 'foo')).to.eql([foo]); + expect (ol.events.getListeners(target, 'bar')).to.eql([bar]); + }); + it('returns undefined when no listeners are registered', function() { + expect (ol.events.getListeners(target, 'foo')).to.be(undefined); + }); + }); + + describe('listen()', function() { + it('calls addEventListener on the target', function() { + ol.events.listen(target, 'foo', function() {}); + expect(add.callCount).to.be(1); + }); + it('adds listeners for multiple types with a single call', function() { + ol.events.listen(target, ['foo', 'bar'], function() {}); + expect(add.getCall(0).args[0]).to.be('foo'); + expect(add.getCall(1).args[0]).to.be('bar'); + }); + it('returns a key', function() { + var key = ol.events.listen(target, 'foo', function() {}); + expect(key).to.be.a(Object); + key = ol.events.listen(target, ['foo', 'bar'], function() {}); + expect(key).to.be.a(Array); + }); + it('does not add the same listener twice', function() { + var listener = function() {}; + var key1 = ol.events.listen(target, 'foo', listener); + var key2 = ol.events.listen(target, 'foo', listener); + expect(key1).to.equal(key2); + expect(add.callCount).to.be(1); + }); + it('only treats listeners as same when all args are equal', function() { + var listener = function() {}; + ol.events.listen(target, 'foo', listener, false); + ol.events.listen(target, 'foo', listener, true); + ol.events.listen(target, 'foo', listener, true, {}); + expect(add.callCount).to.be(3); + }); + }); + + describe('listenOnce()', function() { + it('creates a one-off listener', function() { + var listener = sinon.spy(); + var key = ol.events.listenOnce(target, 'foo', listener); + expect(add.callCount).to.be(1); + expect(key.callOnce).to.be(true); + key.boundListener(); + expect(listener.callCount).to.be(1); + expect(remove.callCount).to.be(1); + }); + it('does not add the same listener twice', function() { + var listener = function() {}; + var key1 = ol.events.listenOnce(target, 'foo', listener); + var key2 = ol.events.listenOnce(target, 'foo', listener); + expect(key1).to.equal(key2); + expect(add.callCount).to.be(1); + expect(key1.callOnce).to.be(true); + }); + it('listen() can turn a one-off listener into a permanent one', function() { + var listener = sinon.spy(); + var key = ol.events.listenOnce(target, 'foo', listener); + expect(key.callOnce).to.be(true); + key = ol.events.listen(target, 'foo', listener); + expect(add.callCount).to.be(1); + expect(key.callOnce).to.be(false); + key.boundListener(); + expect(remove.callCount).to.be(0); + }); + }); + + describe('unlisten()', function() { + it('unregisters previously registered listeners', function() { + var listener = function() {}; + ol.events.listen(target, 'foo', listener); + ol.events.unlisten(target, 'foo', listener); + expect(ol.events.getListeners(target, 'foo')).to.be(undefined); + }); + it('works with multiple types', function() { + var listener = function() {}; + ol.events.listen(target, ['foo', 'bar'], listener); + ol.events.unlisten(target, ['bar', 'foo'], listener); + expect(ol.events.getListeners(target, 'foo')).to.be(undefined); + expect(ol.events.getListeners(target, 'bar')).to.be(undefined); + }); + }); + + describe('unlistenByKey()', function() { + it('unregisters previously registered listeners', function() { + var key = ol.events.listen(target, 'foo', function() {}); + ol.events.unlistenByKey(key); + expect(ol.events.getListeners(target, 'foo')).to.be(undefined); + }); + it('works with multiple types', function() { + var key = ol.events.listen(target, ['foo', 'bar'], function() {}); + ol.events.unlistenByKey(key); + expect(ol.events.getListeners(target, 'foo')).to.be(undefined); + expect(ol.events.getListeners(target, 'bar')).to.be(undefined); + }); + }); + + describe('unlistenAll()', function() { + it('unregisters all listeners registered for a target', function() { + var key = ol.events.listen(target, ['foo', 'bar'], function() {}); + ol.events.unlistenAll(target); + expect(ol.events.getListeners(target, 'foo')).to.be(undefined); + expect(ol.events.getListeners(target, 'bar')).to.be(undefined); + expect(ol.events.LISTENER_MAP_PROP_ in target).to.be(false); + expect(key).to.eql([{}, {}]); + }); + }); + + describe('Compatibility with ol.events.EventTarget', function() { + it('works despite different meaning of the useCapture arg', function() { + var target = new ol.events.EventTarget(); + var listener = function() {}; + var key1 = ol.events.listen(target, 'foo', listener, false); + expect(target.getListeners('foo')).to.eql([key1.boundListener]); + var key2 = ol.events.listen(target, 'foo', listener, true); + expect(target.getListeners('foo')).to.eql( + [key1.boundListener, key2.boundListener]); + }); + it('because the created bound listeners are different', function() { + var listener = function() {}; + var key1 = ol.events.listen(target, 'foo', listener, false); + var key2 = ol.events.listen(target, 'foo', listener, true); + expect(key1.boundListener).to.not.equal(key2.boundListener); + }); + }); + +}); + + +goog.require('ol.events'); +goog.require('ol.events.EventTarget'); diff --git a/test/spec/ol/pointer/mousesource.test.js b/test/spec/ol/pointer/mousesource.test.js index 47001a059e..b5197d75e6 100644 --- a/test/spec/ol/pointer/mousesource.test.js +++ b/test/spec/ol/pointer/mousesource.test.js @@ -75,7 +75,7 @@ describe('ol.pointer.MouseSource', function() { touches: touches, changedTouches: touches }; - ol.events.fireListeners(target, type, event); + target.dispatchEvent(event); } function simulateEvent(type, x, y) { @@ -85,7 +85,7 @@ describe('ol.pointer.MouseSource', function() { clientY: y, target: target }; - ol.events.fireListeners(target, type, event); + target.dispatchEvent(event); } }); diff --git a/test/spec/ol/pointer/pointereventhandler.test.js b/test/spec/ol/pointer/pointereventhandler.test.js index d35f68ec1c..4c92d08324 100644 --- a/test/spec/ol/pointer/pointereventhandler.test.js +++ b/test/spec/ol/pointer/pointereventhandler.test.js @@ -35,7 +35,7 @@ describe('ol.pointer.PointerEventHandler', function() { clientY: y, target: target }; - ol.events.fireListeners(target, type, event); + target.dispatchEvent(event); } describe('pointer down', function() { diff --git a/test/spec/ol/pointer/touchsource.test.js b/test/spec/ol/pointer/touchsource.test.js index 044d03bead..304ec45a91 100644 --- a/test/spec/ol/pointer/touchsource.test.js +++ b/test/spec/ol/pointer/touchsource.test.js @@ -125,7 +125,7 @@ describe('ol.pointer.TouchSource', function() { touches: touches, changedTouches: changedTouches }); - ol.events.fireListeners(target, type, event); + target.dispatchEvent(event); } });