diff --git a/.gitignore b/.gitignore index f92cfe6..d85ee47 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,6 @@ tmp/ dist/ *.log *.tap +tests/functional/css +tests/functional/bundle.js +tests/functional/subscribe-functional.js diff --git a/.travis.yml b/.travis.yml index 618202a..fd01cd2 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,6 @@ sudo: false language: node_js -matrix: - allow_failures: - - node_js: "0.13" node_js: - - "iojs" - - "0.13" - "0.12" after_success: - "npm run func" diff --git a/Gruntfile.js b/Gruntfile.js index d3f08a8..d4e7bfb 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -128,6 +128,7 @@ module.exports = function (grunt) { { expand: true, src: [ + '<%= project.unit %>/../lib/**/*.*', '<%= project.unit %>/**/*.*' ], dest: '<%= project.tmp %>', diff --git a/README.md b/README.md index a8e6a7b..3b96a63 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ npm install subscribe-ui-event Subscription subscribe(String eventType, Function callback, Object? options) ``` -Provide throttled version of window or document events, such like `scroll`, `resize` and `visibilitychange` to subscribe. It also provides some higher, compound events, such like `viewportchange`, which combines `scroll`, `resize` and `visibilitychange` events. +Provide throttled version of window or document events, such like `scroll`, `resize` and `visibilitychange` to subscribe. **Note on IE8 or the below, the throttle will be turned off because the event object is global and will be deleted for setTimeout or rAF.** @@ -102,16 +102,7 @@ The format of the payload is: 4. resize - window.resize 5. resizeStart - The start window.resize 6. resizeEnd - The end window.resize -7. visibilitychange - document.visibilitychange -8. viewportchange - scroll + resize + visibilitychange - -### unsubscribe - -```js -Void unsubscribe(String eventType, Function callback) -``` - -Unsubscribe an event. **Note that all subscriptions with the same eventHandler and the same event type will be unsubscribed together even if they have different options**. +7. visibilitychange - document.visibilitychange (IE8 doesn't support) ## License diff --git a/index.js b/index.js index 047ae1c..9ec5a6d 100644 --- a/index.js +++ b/index.js @@ -12,12 +12,10 @@ function warn() { if (typeof window !== 'undefined') { module.exports = { - subscribe: require('./dist/subscribe'), - unsubscribe: require('./dist/unsubscribe') + subscribe: require('./dist/subscribe') }; } else { module.exports = { - subscribe: warn, - unsubscribe: warn + subscribe: warn }; } diff --git a/package.json b/package.json index d3822a2..09878fe 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "subscribe-ui-event", - "version": "0.2.10", + "version": "1.0.0", "description": "A single, throttle built-in solution to subscribe to browser UI Events.", "main": "index.js", "scripts": { @@ -25,6 +25,7 @@ "dependencies": { "eventemitter3": "^1.1.0", "lodash.throttle": "^3.0.3", + "lodash.clone": "^3.0.3", "raf": "^3.0.0" }, "devDependencies": { @@ -45,11 +46,11 @@ "grunt-webpack": "^1.0.8", "grunt": "^0.4.5", "istanbul": "^0.3.0", - "jsdom": "^6.0.0", "jshint": "^2.5.1", "minimist": "^1.0.0", "mocha": "^2.0", "mockery": "^1.4.0", + "node-jsdom": "^3.0.0", "pre-commit": "^1.0.0", "react": "^0.14.0", "react-dom": "^0.14.0", diff --git a/src/AugmentedEvent.js b/src/AugmentedEvent.js index ff6241a..1a0f43c 100644 --- a/src/AugmentedEvent.js +++ b/src/AugmentedEvent.js @@ -4,14 +4,28 @@ */ 'use strict'; -var scroll = { - delta: 0, - top: 0 -}; +var globalVars = require('./globalVars'); var resize = { width: 0, height: 0 }; +var scroll = { + delta: 0, + top: 0 +}; + +// global variables +var doc; +var docBody; +var docEl; +var win; + +if (typeof window !== 'undefined') { + win = window; + doc = win.document || document; + docEl = doc.documentElement; + docBody = doc.body; +} /** * ArgmentedEvent will hold some global information, such like window scroll postion, @@ -25,4 +39,26 @@ function ArgmentedEvent(option) { this.resize = resize; } +ArgmentedEvent.prototype = { + update: function update (mainType) { + var top; + + if (globalVars.enableScrollInfo && + (mainType === 'scroll' || mainType === 'touchmove') + ) { + top = docEl.scrollTop + docBody.scrollTop; + // Prevent delta from being 0 + if (top !== this.scroll.top) { + this.scroll.delta = top - this.scroll.top; + this.scroll.top = top; + console.log(top); + } + } + if (globalVars.enableResizeInfo && mainType === 'resize') { + this.resize.width = win.innerWidth || docEl.clientWidth; + this.resize.height = win.innerHeight || docEl.clientHeight; + } + } +}; + module.exports = ArgmentedEvent; diff --git a/src/constants.js b/src/constants.js new file mode 100644 index 0000000..0ce97bf --- /dev/null +++ b/src/constants.js @@ -0,0 +1,10 @@ +/** + * Copyright 2015, Yahoo! Inc. + * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ +'use strict'; + +module.exports = { + EVENT_END_DELAY: 200, + DEFAULT_THROTTLE_RATE: 50 +}; diff --git a/src/eventHandlers/index.js b/src/eventHandlers/index.js deleted file mode 100644 index b00ad60..0000000 --- a/src/eventHandlers/index.js +++ /dev/null @@ -1,224 +0,0 @@ -/** - * Copyright 2015, Yahoo! Inc. - * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. - */ -/* global window, document */ - -'use strict'; - -var AugmentedEvent = require('../AugmentedEvent'); -var ee = require('../eventEmitter').eventEmitter; -var ehs = require('../eventEmitter').eventHandlers; -var leIE8 = require('../lib/leIE8'); - -// constants -var EVENT_END_DELAY = 200; - -// global variables -var doc; -var docBody; -var docEl; -var enableResizeInfo = false; -var enableScrollInfo = false; -var win; - -if (typeof window !== 'undefined' && typeof document !== 'undefined') { - win = window; - doc = document; - docEl = doc.documentElement; - docBody = doc.body; -} - -/** - * This is designed for cloning event object and IE8 event object doesn't have hasOwnProperty(), - * so no hasOwnProperty() being used in for loop. - * @method copyEventObj - * @param {Object} o - The object to be cloned. - * @return {Object} The cloned object. - */ -function copyEventObj(o) { - var r = {}; - for (var i in o) { - // jslint will compain if there is no 'if' in for-in loop. - if (true) { - r[i] = o[i]; - } - } - return r; -} - -/** - * Update global scroll/resize info - * @param {Object} ae - The additional event object - * @param {String} eventType - The event type - */ -function updateAdditionalInfo(ae, eventType) { - var top; - if (enableScrollInfo && eventType === 'scroll') { - top = docEl.scrollTop + docBody.scrollTop; - // Prevent delta from being 0 - if (top !== ae.scroll.top) { - ae.scroll.delta = top - ae.scroll.top; - ae.scroll.top = top; - } - } else if (enableResizeInfo && eventType === 'resize') { - ae.resize.width = win.innerWidth || docEl.clientWidth; - ae.resize.height = win.innerHeight || docEl.clientHeight; - } -} - -/** - * Cross-browser addEventListener. - * @method listen - * @param {Object} target - The target to add event listener. - * @param {String} eventType - The event type. - * @param {Function} handler - The event handler. - * @return {Object} The object to be able to remove the handler. - */ -function listen(target, eventType, handler) { - var add = 'addEventListener'; - var remove = 'removeEventListener'; - - if (!target.addEventListener && target.attachEvent) { - add = 'attachEvent'; - remove = 'detachEvent'; - eventType = 'on' + eventType; - } - target[add](eventType, handler, false); - - return { - remove: function() { - target[remove](eventType, handler); - } - }; -} - -/** - * Generate the start or end of those continuous event, such like scroll and resize. - * @method generateEdgeEventHandler - * @param {Object} target - The event target, usually window or document. - * @param {String} eventType - The event type. - * @param {Boolean} eventStart - true for the event start, otherwise the event end. - * @return {Function} The function to generate throttle event. - */ -function generateEdgeEventHandler(target, eventType, eventStart) { - return function(eeType, options) { - // One subscription needs scroll/resize info, all will get those information - enableScrollInfo = enableScrollInfo || options.enableScrollInfo; - enableResizeInfo = enableResizeInfo || options.enableResizeInfo; - - if (ee.listeners(eeType, true)) { - return; - } - - var throttleRate = options.throttleRate; - var throttle = options.throttleFunc; - var ae = new AugmentedEvent({type: eventType + (eventStart ? 'Start' : 'End')}); - var timer; - - function eventEndCallback(e) { - if (!eventStart) { - updateAdditionalInfo(ae, eventType); - ee.emit(eeType, e, ae); - } - timer = null; - } - - function eventHandler(e) { - if (!timer) { - if (eventStart) { - updateAdditionalInfo(ae, eventType); - ee.emit(eeType, e, ae); - } - } - - clearTimeout(timer); - if (leIE8) { - e = copyEventObj(e); - } - - if (eventEndCallback.bind) { - timer = setTimeout(eventEndCallback.bind(null, e), throttleRate + EVENT_END_DELAY); - } else { - timer = setTimeout(function eventEndDelay() { - eventEndCallback(e); - }, throttleRate + EVENT_END_DELAY); - } - } - - var handler = throttle(eventHandler, throttleRate); - ehs[eeType] = listen(target, eventType, handler); - }; -} - -/** - * Generate thea continuous event, such like scroll and resize. - * @method generateContinuousEventHandler - * @param {Object} target - The event target, usually window or document. - * @param {String} eventType - The event type. - * @param {Boolean} eventStart - true for the event start, otherwise the event end. - * @return {Function} The function to generate throttle event. - */ -function generateContinuousEventHandler(target, eventType, noThrottle) { - return function(eeType, options) { - // One subscription needs scroll/resize info, all will get those information - enableScrollInfo = enableScrollInfo || options.enableScrollInfo; - enableResizeInfo = enableResizeInfo || options.enableResizeInfo; - - if (ee.listeners(eeType, true)) { - return; - } - - var throttleRate = options.throttleRate; - var throttle = options.throttleFunc; - var ae = new AugmentedEvent({type: eventType}); - - function eventHandler(e) { - updateAdditionalInfo(ae, eventType); - ee.emit(eeType, e, ae); - } - - var handler = (!noThrottle && throttleRate > 0) ? throttle(eventHandler, throttleRate) : eventHandler; - ehs[eeType] = listen(target, eventType, handler); - }; -} - -function viewportchange(eeType, options) { - if (ee.listeners(eeType, true)) { - return; - } - - var throttleRate = options.throttleRate; - var throttle = options.throttleFunc; - var ae = new AugmentedEvent({type: 'viewportchange'}); - function eventHandler(e) { - ee.emit(eeType, e, ae); - } - - var handler = throttleRate > 0 ? throttle(eventHandler, throttleRate) : eventHandler; - - ehs[eeType] = { - remove: function () { - for (var i = 0, l = this._handlers.length; i < l; i++) { - this._handlers[i].remove(); - } - }, - _handlers: [ - listen(win, 'scroll', handler), - listen(win, 'resize', handler), - // no throttle for visibilitychange, otherwise will call twice - listen(win, 'visibilitychange', eventHandler) - ] - }; -} - -module.exports = { - resize: generateContinuousEventHandler(win, 'resize'), - resizeEnd: generateEdgeEventHandler(win, 'resize', false), - resizeStart: generateEdgeEventHandler(win, 'resize', true), - scroll: generateContinuousEventHandler(win, 'scroll'), - scrollEnd: generateEdgeEventHandler(win, 'scroll', false), - scrollStart: generateEdgeEventHandler(win, 'scroll', true), - viewportchange: viewportchange, - visibilitychange: generateContinuousEventHandler(doc, 'visibilitychange', true) -}; diff --git a/src/eventEmitter.js b/src/globalVars.js similarity index 63% rename from src/eventEmitter.js rename to src/globalVars.js index 2a83ff5..96446bc 100644 --- a/src/eventEmitter.js +++ b/src/globalVars.js @@ -7,7 +7,9 @@ var EventEmitter = require('eventemitter3'); module.exports = { - eventEmitter: new EventEmitter(), - eventHandlers: {}, - subscriptions: [] + EE: new EventEmitter(), + connections: {}, + listeners: {}, + enableScrollInfo: false, + enableResizeInfo: false }; diff --git a/src/lib/listen.js b/src/lib/listen.js new file mode 100644 index 0000000..ffd6e59 --- /dev/null +++ b/src/lib/listen.js @@ -0,0 +1,33 @@ +/** + * Copyright 2015, Yahoo! Inc. + * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ +'use strict'; + +/** + * Cross-browser addEventListener. + * @method listen + * @param {Object} target - The target to add event listener. + * @param {String} eventType - The event type. + * @param {Function} handler - The event handler. + * @return {Object} The object to be able to remove the handler. + */ +function listen(target, eventType, handler) { + var add = 'addEventListener'; + var remove = 'removeEventListener'; + + if (!target.addEventListener && target.attachEvent) { + add = 'attachEvent'; + remove = 'detachEvent'; + eventType = 'on' + eventType; + } + target[add](eventType, handler, false); + + return { + remove: function() { + target[remove](eventType, handler); + } + }; +} + +module.exports = listen; diff --git a/src/lib/rAFThrottle.js b/src/lib/rAFThrottle.js index dfdd915..09b4755 100644 --- a/src/lib/rAFThrottle.js +++ b/src/lib/rAFThrottle.js @@ -14,6 +14,8 @@ function rAFThrottle(func, throttle) { var last = 0; var requestId = 0; + throttle = throttle || 15; + var later = function () { var now = getTime(); var remaining = throttle - (now - last); diff --git a/src/mainEventConnectors.js b/src/mainEventConnectors.js new file mode 100644 index 0000000..986d263 --- /dev/null +++ b/src/mainEventConnectors.js @@ -0,0 +1,171 @@ +/** + * Copyright 2015, Yahoo! Inc. + * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ + /* global window, document, setTimeout */ +'use strict'; + +var _clone = require('lodash.clone'); +var _throttle = require('lodash.throttle'); +var AugmentedEvent = require('./AugmentedEvent'); +var connections = require('./globalVars').connections; +var EE = require('./globalVars').EE; +var globalVars = require('./globalVars'); +var leIE8 = require('./lib/leIE8'); +var listen = require('./lib/listen'); +var listeners = require('./globalVars').listeners; +var rAFThrottle = require('./lib/rAFThrottle'); +var subscriptions = require('./globalVars').subscriptions; + +// constants +var EVENT_END_DELAY = require('./constants').EVENT_END_DELAY; + +// global variables +var doc; +var win; + +if (typeof window !== 'undefined') { + win = window; + doc = win.document || document; +} + +/** + * Connect a throttled event to a throttled main event, and return an event remover. + * The number of connections to a throttled main event will be kept. If all throttled events + * are removed, then remove throttled main event. + * @method connectThrottle + * @param {String} throttledEvent - A throttled event + * @param {Function} cb - Callback function + * @param {Object} ctx - The "this" + * @param {String} throttledMainEvent - A throttled main event + * @return {Object} An event remover + */ +function connectThrottle (throttledEvent, cb, ctx, throttledMainEvent) { + EE.on(throttledEvent, cb, ctx); + throttledMainEvent = throttledMainEvent || throttledEvent; + connections[throttledMainEvent] = (connections[throttledMainEvent] || 0) + 1; + return { + _type: throttledEvent, + _cb: cb, + _ctx: ctx, + unsubscribe: function unsubscribe () { + if (!this._type) { + return; + } + + EE.removeListener(throttledEvent, cb, ctx); + connections[throttledMainEvent]--; + if (connections[throttledMainEvent] === 0) { + listeners[throttledMainEvent].remove(); + listeners[throttledMainEvent] = undefined; + } + + this._type = undefined; + this._cb = undefined; + this._ctx = undefined; + } + }; +} + +/** + * Connect to event, event start and event end. + * @method connectContinuousEvent + * @param {Object} target - The target of a main event, window or document. + * @param {String} mainEvent - A browser event, like scroll or resize. + * @param {String} event - A subscribe event. + */ +function connectContinuousEvent (target, mainEvent, event) { + return function throttleEvent (throttleRate, cb, context) { + var throttledStartEvent = mainEvent + 'Start:' + throttleRate; + var throttledEndEvent = mainEvent + 'End:' + throttleRate; + var throttledMainEvent = mainEvent + ':' + throttleRate; + var throttledEvent = event + ':' + throttleRate; + + var remover = connectThrottle(throttledEvent, cb, context, throttledMainEvent); + + if (listeners[throttledMainEvent]) { + return remover; + } + + var ae = { + start: new AugmentedEvent({type: mainEvent + 'Start'}), // start + main: new AugmentedEvent({type: mainEvent}), // main + end: new AugmentedEvent({type: mainEvent + 'End'}), // end + }; + + // No throttle for throttleRate = 0 + if (throttleRate === 'raf') { + throttleRate = 16; // Set as a number for setTimeout later. + handler = rAFThrottle(handler); + } else if (throttleRate > 0) { + handler = _throttle(handler, throttleRate); + } + + var timer; + function endCallback (e) { + ae.end.update(mainEvent); + EE.emit(throttledEndEvent, e, ae.end); + timer = null; + } + function handler (e) { + ae.start.update(mainEvent); + if (!timer) { + EE.emit(throttledStartEvent, e, ae.start); + } + clearTimeout(timer); + + // No need to call ae.main.update(), because ae.start.update is called, everything is update-to-date. + EE.emit(throttledMainEvent, e, ae.main); + if (!leIE8) { + timer = setTimeout(endCallback.bind(null, e), throttleRate + EVENT_END_DELAY); + } else { + // For browser less then and equal to IE8, event object need to be cloned for setTimeout. + e = _clone(e); + timer = setTimeout(function eventEndDelay() { + endCallback(e); + }, throttleRate + EVENT_END_DELAY); + } + } + + listeners[throttledMainEvent] = listen(target, mainEvent, handler); + return remover; + }; +} + +function connectDiscreteEvent (target, event) { + return function throttleEvent (throttleRate, cb, context) { + // no throttling for discrete event + var throttledEvent = event + ':0'; + + var remover = connectThrottle(throttledEvent, cb, context); + + if (listeners[throttledEvent]) { + return remover; + } + + var ae = new AugmentedEvent({type: event}); + + function handler (e) { + ae.update(event); + EE.emit(throttledEvent, e, ae); + } + + listeners[throttledEvent] = listen(target, event, handler); + return remover; + }; +} + +module.exports = { + scrollStart: connectContinuousEvent(win, 'scroll', 'scrollStart'), + scrollEnd: connectContinuousEvent(win, 'scroll', 'scrollEnd'), + scroll: connectContinuousEvent(win, 'scroll', 'scroll'), + resizeStart: connectContinuousEvent(win, 'resize', 'resizeStart'), + resizeEnd: connectContinuousEvent(win, 'resize', 'resizeEnd'), + resize: connectContinuousEvent(win, 'resize', 'resize'), + visibilitychange: connectDiscreteEvent(doc, 'visibilitychange'), + touchmoveStart: connectContinuousEvent(win, 'touchmove', 'touchmoveStart'), + touchmoveEnd: connectContinuousEvent(win, 'touchmove', 'touchmoveEnd'), + touchmove: connectContinuousEvent(win, 'touchmove', 'touchmove'), + touchstart: connectDiscreteEvent(doc, 'touchstart'), + touchend: connectDiscreteEvent(doc, 'touchend'), +}; diff --git a/src/subscribe.js b/src/subscribe.js index 61a0760..33a69c8 100644 --- a/src/subscribe.js +++ b/src/subscribe.js @@ -4,46 +4,35 @@ */ 'use strict'; -var ee = require('./eventEmitter').eventEmitter; -var ehs = require('./eventEmitter').eventHandlers; -var emptyFunction = function () {}; -var eventHandlers = require('./eventHandlers'); +var globalVars = require('./globalVars'); var leIE8 = require('./lib/leIE8'); // less then or equal to IE8 -var rAFThrottle = require('./lib/rAFThrottle'); -var subscriptions = require('./eventEmitter').subscriptions; -var throttle = require('lodash.throttle'); +var mainEventConnectors = require('./mainEventConnectors'); + +// constants +var DEFAULT_THROTTLE_RATE = require('./constants').DEFAULT_THROTTLE_RATE; /** * Subscribe to UI events. * @method subscribe - * @param {String} eventType - The type of event. + * @param {String} type - The type of event. * @param {Function} cb - The callback function. * @param {Object} options.context - The caller. * @param {Number} options.throttleRate - The amount of time for throttling. * @param {Boolean} options.useRAF - Use rAF for throttling if true. * @return {Object} The object with unsubscribe function. */ -function subscribe(eventType, cb, options) { - if (!eventHandlers[eventType] || !cb) { - return { - unsubscribe: emptyFunction - }; - } - +function subscribe(type, cb, options) { options = options || {}; - var context = options.context || null; - var eeType; // emitEmitterType = eventType + ':' + throttle - var enableScrollInfo = options.enableScrollInfo || false; - var enableResizeInfo = options.enableResizeInfo || false; - var sub; - var throttleFunc; - var throttleRate = parseInt(options.throttleRate); var useRAF = options.useRAF || false; + var throttleRate = parseInt(options.throttleRate, 10); - throttleFunc = useRAF ? rAFThrottle : throttle; if (isNaN(throttleRate)) { - throttleRate = useRAF ? 15 : 50; // 15ms will be equivalent to 1 rAF + throttleRate = DEFAULT_THROTTLE_RATE; + } + + if (useRAF) { + throttleRate = 'raf'; } // turn off throttle if the browser is IE8 or less, because window.event will be reset @@ -52,40 +41,11 @@ function subscribe(eventType, cb, options) { throttleRate = 0; } - // eeType is throttled event type, such like "scroll:50", meaning scroll event with 50ms throttle rate - eeType = eventType + ':' + throttleRate + (useRAF ? ':raf' : ''); - - // wire UI event to throttled event, for example, wire "window.scroll" to "scroll:50" - // add event listeners to UI event for the same throttled event - eventHandlers[eventType](eeType, { - enableScrollInfo: enableScrollInfo, - enableResizeInfo: enableResizeInfo, - throttleFunc: throttleFunc, - throttleRate: throttleRate - }); - - // wire to throttled event - ee.on(eeType, cb, context); - - // append sub to subscriptions - sub = { - _cb: cb, - _eventType: eventType, - unsubscribe: function () { - var i = subscriptions.indexOf(sub); - ee.removeListener(eeType, cb); - if (i !== -1) { - subscriptions.splice(i, 1); - } - if (!ee.listeners(eeType, true) && ehs[eeType]) { - ehs[eeType].remove(); - ehs[eeType] = undefined; - } - } - }; - subscriptions.push(sub); + // once those variables enabled, then never disabled. + globalVars.enableScrollInfo = globalVars.enableScrollInfo || options.enableScrollInfo || false; + globalVars.enableResizeInfo = globalVars.enableResizeInfo || options.enableResizeInfo || false; - return sub; + return mainEventConnectors[type](throttleRate, cb, options.context); } module.exports = subscribe; diff --git a/src/unsubscribe.js b/src/unsubscribe.js deleted file mode 100644 index af2e2e5..0000000 --- a/src/unsubscribe.js +++ /dev/null @@ -1,26 +0,0 @@ -/** - * Copyright 2015, Yahoo! Inc. - * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. - */ -'use strict'; - -var subscriptions = require('./eventEmitter').subscriptions; - -/** - * Unsubscribe UI events. Note that all subscriptions having the same eventHandler and the same event type - * will be unsubscribed together even if they have different options. - * @method unsubscribe - * @param {String} eventType - The type of event - * @param {Function} cb - The callback function - */ -function unsubscribe(eventType, cb) { - var sub; - for (var i = subscriptions.length - 1; i >= 0; i--) { - sub = subscriptions[i]; - if (sub._eventType === eventType && sub._cb === cb) { - sub.unsubscribe(); - } - } -} - -module.exports = unsubscribe; diff --git a/tests/functional/subscribe-functional.jsx b/tests/functional/subscribe-functional.jsx index 413b417..a701988 100644 --- a/tests/functional/subscribe-functional.jsx +++ b/tests/functional/subscribe-functional.jsx @@ -30,7 +30,8 @@ var EventCounter = React.createClass({ if (!this.props.disable) { subscribe(this.props.eventType, this._handleEvent, { throttleRate: this.props.throttleRate, - useRAF: this.props.useRAF + useRAF: this.props.useRAF, + enableScrollInfo: true }); } }, @@ -62,11 +63,15 @@ var SubscribeDemo = React.createClass({ - - + + + + + + ); } diff --git a/tests/functional/subscribe.spec.js b/tests/functional/subscribe.spec.js index 2e247bb..6debeb6 100644 --- a/tests/functional/subscribe.spec.js +++ b/tests/functional/subscribe.spec.js @@ -30,18 +30,15 @@ describe('Subscribe UI Event tests', function () { function test(cb) { expect($('.scroll-0').getInt()).to.not.below(20, 'scroll-0'); - expect($('.viewportchange-0').getInt()).to.not.below(20, 'viewportchange-0'); if (!leIE8) { expect($('.scroll-raf').getInt()).to.not.above(20, 'scroll-raf'); expect($('.scroll-1000').getInt()).to.below(20, 'scroll-1000'); - expect($('.scroll-300-raf').getInt()).to.below(20, 'scroll-300-raf'); - expect($('.viewportchange-1000').getInt()).to.below(20, 'viewportchange-1000'); + expect($('.scroll-300-raf').getInt()).to.not.above(20, 'scroll-300-raf'); } else { expect($('.scroll-raf').getInt()).to.not.below(20, 'scroll-raf'); expect($('.scroll-1000').getInt()).to.not.below(20, 'scroll-1000'); expect($('.scroll-300-raf').getInt()).to.not.below(20, 'scroll-300-raf'); - expect($('.viewportchange-1000').getInt()).to.not.below(20, 'viewportchange-1000'); } expect($('.scrollStart').getInt()).to.equal(1, 'scrollStart'); diff --git a/tests/lib/setup.js b/tests/lib/setup.js new file mode 100644 index 0000000..a3982ef --- /dev/null +++ b/tests/lib/setup.js @@ -0,0 +1,55 @@ +/** + * Copyright 2015, Yahoo! Inc. + * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. + */ +/* global describe, it */ + +'use strict'; + +var jsdom = require('node-jsdom'); +global.document = jsdom.jsdom(''); +global.window = document.parentWindow; +global.navigator = window.navigator; +global.Event = window.Event; + +var env = { + eventHandlers: {} +}; + +function addEventListener (type, cb) { + env.eventHandlers[type] = cb; +} + +function removeEventListener (type, cb) { + env.eventHandlers[type] = undefined; +} + +window = { + addEventListener: addEventListener, + removeEventListener: removeEventListener, + attachEvent: addEventListener, + detachEvent: removeEventListener, + setTimeout: function (cb, wait) { + cb(); + }, + requestAnimationFrame: function (cb) { + cb(); + }, + cancelAnimationFrame: function () { + }, + innerWidth: 20 +}; +document = { + documentElement: { + scrollTop: 10 + }, + body: { + scrollTop: 0 + }, + addEventListener: addEventListener, + removeEventListener: removeEventListener, + attachEvent: addEventListener, + detachEvent: removeEventListener +}; + +module.exports = env; diff --git a/tests/unit/subscribe-ie8.js b/tests/unit/subscribe-ie8.js index 867c358..56793b9 100644 --- a/tests/unit/subscribe-ie8.js +++ b/tests/unit/subscribe-ie8.js @@ -2,87 +2,82 @@ * Copyright 2015, Yahoo! Inc. * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. */ -/* global describe, it */ +/* global describe, it, before, beforeEach */ 'use strict'; -var ee = require('../../../src/eventEmitter').eventEmitter; +var env = require('../lib/setup'); + +var globalVars = require('../../../src/globalVars'); +var ee = require('../../../src/globalVars').EE; var expect = require('expect.js'); var subscribe; describe('subscribe-ie8', function () { before(function () { - GLOBAL.window = { - attachEvent: function (eventType, cb) { - eventType = eventType.replace('on', ''); - ee.on(eventType, cb); - }, - detachEvent: function (eventType, cb) { - ee.removeListener(eventType, cb); - }, - setTimeout: function (cb, wait) { - cb(); - } - }; - GLOBAL.document = { - attachEvent: function (eventType, cb) { - ee.on(eventType, cb); - }, - detachEvent: function (eventType, cb) { - ee.removeListener(eventType, cb); + // navigator.userAgent = 'MSIE 1.0'; + Object.defineProperty(navigator, 'userAgent', { + get: function() { + return 'MSIE 1.0'; } - }; - GLOBAL.navigator = { - userAgent: 'MSIE 1.0' - }; + }); + window.addEventListener = null; + window.removeEventListener = null; + document.addEventListener = null; + document.removeEventListener = null; + require.cache[require.resolve('../../../src/lib/leIE8')] = undefined; + require.cache[require.resolve('../../../src/subscribe')] = undefined; + require.cache[require.resolve('../../../src/mainEventConnectors')] = undefined; }); after(function () { - GLOBAL.window = undefined; - GLOBAL.document = undefined; - GLOBAL.navigator = undefined; + Object.defineProperty(navigator, 'userAgent', { + get: function() { + return ''; + } + }); + window.addEventListener = window.attachEvent; + window.removeEventListener = window.detachEvent; + document.addEventListener = document.attachEvent; + document.removeEventListener = document.detachEvent; }); beforeEach(function () { - require.cache[require.resolve('../../../src/eventHandlers')] = undefined; - require.cache[require.resolve('../../../src/lib/leIE8')] = undefined; - require.cache[require.resolve('../../../src/subscribe')] = undefined; subscribe = require('../../../src/subscribe'); - - ee.removeAllListeners('scroll'); - ee.removeAllListeners('resize'); - ee.removeAllListeners('visibilitychange'); }); describe('#subscribe', function () { - it('scroll should be triggered by window scroll', function (done) { + it('scroll should be triggered by window scroll without throttling', function (done) { var subscription = subscribe('scroll', function (e, syntheticEvent) { expect(e.foo).equal('foo'); expect(syntheticEvent.type).equal('scroll'); + expect(subscription._type).equal('scroll:0'); subscription.unsubscribe(); done(); }); // simulate window scroll event - ee.emit('scroll', {foo: 'foo'}); + env.eventHandlers.onscroll({foo: 'foo'}); }); - it('scrollStart and scrollEnd should be triggered by the start/end of window scroll', function (done) { + it('scrollStart and scrollEnd should be triggered by the start/end without throttling', function (done) { var subscription1 = subscribe('scrollStart', function (e, syntheticEvent) { expect(e.foo).equal('foo'); expect(syntheticEvent.type).equal('scrollStart'); + expect(subscription1._type).equal('scrollStart:0'); subscription1.unsubscribe(); }); var subscription2 = subscribe('scrollEnd', function (e, syntheticEvent) { expect(e.foo).equal('foo'); expect(syntheticEvent.type).equal('scrollEnd'); + expect(subscription2._type).equal('scrollEnd:0'); subscription2.unsubscribe(); done(); }); // simulate window scroll event - ee.emit('scroll', {foo: 'foo'}); + env.eventHandlers.onscroll({foo: 'foo'}); }); }); }); diff --git a/tests/unit/subscribe.js b/tests/unit/subscribe.js index 5ce9de9..326f57e 100644 --- a/tests/unit/subscribe.js +++ b/tests/unit/subscribe.js @@ -6,151 +6,106 @@ 'use strict'; -var ee = require('../../../src/eventEmitter').eventEmitter; +var env = require('../lib/setup'); + +var globalVars = require('../../../src/globalVars'); +var ee = require('../../../src/globalVars').EE; var expect = require('expect.js'); -var subscribe; +var subscribe = require('../../../src/subscribe'); describe('subscribe', function () { - before(function () { - GLOBAL.window = { - addEventListener: function (eventType, cb) { - ee.on(eventType, cb); - }, - removeEventListener: function (eventType, cb) { - ee.removeListener(eventType, cb); - }, - setTimeout: function (cb, wait) { - cb(); - }, - innerWidth: 10 - }; - GLOBAL.document = { - documentElement: { - scrollTop: 10 - }, - body: { - scrollTop: 0 - }, - addEventListener: function (eventType, cb) { - ee.on(eventType, cb); - }, - removeEventListener: function (eventType, cb) { - ee.removeListener(eventType, cb); - } - }; - require.cache[require.resolve('../../../src/eventHandlers')] = undefined; - require.cache[require.resolve('../../../src/lib/leIE8')] = undefined; - require.cache[require.resolve('../../../src/subscribe')] = undefined; - }); - - after(function () { - GLOBAL.window = undefined; - GLOBAL.document = undefined; - }); - - beforeEach(function () { - subscribe = require('../../../src/subscribe'); - - ee.removeAllListeners('scroll'); - ee.removeAllListeners('resize'); - ee.removeAllListeners('visibilitychange'); - }); - describe('#subscribe', function () { it('scroll should be triggered by window scroll', function (done) { - var subscription = subscribe('scroll', function (e, syntheticEvent) { + var subscription = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scroll'); + expect(ae.type).equal('scroll'); subscription.unsubscribe(); done(); }); // simulate window scroll event - ee.emit('scroll', {foo: 'foo'}); - }); - - it('by default scroll should be triggered by scroll:50 (scroll with 50ms throttle)', function (done) { - var subscription = subscribe('scroll', function (e, syntheticEvent) { - expect(e.foo).equal('foo'); - expect(syntheticEvent.bar).equal('bar'); - subscription.unsubscribe(); - done(); - }); - - ee.emit('scroll:50', {foo: 'foo'}, {bar: 'bar'}); + env.eventHandlers.scroll({foo: 'foo'}); }); it('scroll with throttle = 100 should be triggered by scroll:100 (scroll with 100ms throttle)', function (done) { - var subscription = subscribe('scroll', function (e, syntheticEvent) { + var subscription = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.bar).equal('bar'); + expect(ae.type).equal('scroll'); + expect(subscription._type).equal('scroll:100'); subscription.unsubscribe(); done(); }, {throttleRate: 100}); - ee.emit('scroll:100', {foo: 'foo'}, {bar: 'bar'}); + // simulate window scroll event + env.eventHandlers.scroll({foo: 'foo'}); }); it('scroll with rAF throttle should be triggered by window scroll', function (done) { - var subscription = subscribe('scroll', function (e, syntheticEvent) { + var subscription = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scroll'); + expect(ae.type).equal('scroll'); + expect(subscription._type).equal('scroll:raf'); subscription.unsubscribe(); done(); }, {useRAF: true}); - ee.emit('scroll', {foo: 'foo'}); - }); - - it('by default scroll with rAF throttle should be triggered by scroll:15:raf', function (done) { - var subscription = subscribe('scroll', function (e, syntheticEvent) { - expect(e.foo).equal('foo'); - expect(syntheticEvent.bar).equal('bar'); - subscription.unsubscribe(); - done(); - }, {useRAF: true}); - - ee.emit('scroll:15:raf', {foo: 'foo'}, {bar: 'bar'}); + // simulate window scroll event + env.eventHandlers.scroll({foo: 'foo'}); }); - it('scroll with 50ms rAF throttle should be triggered by scroll:50:raf', function (done) { - var subscription = subscribe('scroll', function (e, syntheticEvent) { + it('scroll with 50ms rAF throttle should be triggered by scroll:raf', function (done) { + var subscription = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.bar).equal('bar'); + expect(ae.type).equal('scroll'); + expect(subscription._type).equal('scroll:raf'); subscription.unsubscribe(); done(); }, {throttleRate: 50, useRAF: true}); - ee.emit('scroll:50:raf', {foo: 'foo'}, {bar: 'bar'}); - }); - - it('viewportchange should be triggered by scroll, resize, and visibilitychange', function (done) { - var fireCount = 0; - var subscription = subscribe('viewportchange', function (e, syntheticEvent) { - expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('viewportchange'); - fireCount++; - if (fireCount === 3) { - subscription.unsubscribe(); - done(); - } - }); - - ee.emit('scroll', {foo: 'foo'}); - ee.emit('resize', {foo: 'foo'}); - ee.emit('visibilitychange', {foo: 'foo'}); + // simulate window scroll event + env.eventHandlers.scroll({foo: 'foo'}); }); - it('viewportchange should be triggered by viewportchange:50', function (done) { - var subscription = subscribe('viewportchange', function (e, syntheticEvent) { + // it('viewportchange should be triggered by scroll, resize, and visibilitychange', function (done) { + // var fireCount = 0; + // var subscription = subscribe('viewportchange', function (e, ae) { + // expect(e.foo).equal('foo'); + // expect(ae.type).equal('viewportchange'); + // fireCount++; + // if (fireCount === 3) { + // subscription.unsubscribe(); + // done(); + // } + // }); + // + // ee.emit('scroll', {foo: 'foo'}); + // ee.emit('resize', {foo: 'foo'}); + // ee.emit('visibilitychange', {foo: 'foo'}); + // }); + // + // it('viewportchange should be triggered by viewportchange:50', function (done) { + // var subscription = subscribe('viewportchange', function (e, ae) { + // expect(e.foo).equal('foo'); + // expect(ae.bar).equal('bar'); + // subscription.unsubscribe(); + // done(); + // }); + // + // ee.emit('viewportchange:50', {foo: 'foo'}, {bar: 'bar'}); + // }); + + it('visibilitychange should be triggered by visibilitychange:0', function (done) { + // no throttling for discrete event + var subscription = subscribe('visibilitychange', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.bar).equal('bar'); + expect(ae.type).equal('visibilitychange'); + expect(subscription._type).equal('visibilitychange:0'); subscription.unsubscribe(); done(); }); - ee.emit('viewportchange:50', {foo: 'foo'}, {bar: 'bar'}); + env.eventHandlers.visibilitychange({foo: 'foo'}); }); it('should not fail if pass null arguments', function () { @@ -159,147 +114,154 @@ describe('subscribe', function () { }); it('scrollStart and scrollEnd should be triggered by the start/end of window scroll', function (done) { - var subscription1 = subscribe('scrollStart', function (e, syntheticEvent) { + var subscription1 = subscribe('scrollStart', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scrollStart'); + expect(ae.type).equal('scrollStart'); subscription1.unsubscribe(); }); - var subscription2 = subscribe('scrollEnd', function (e, syntheticEvent) { + var subscription2 = subscribe('scrollEnd', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scrollEnd'); + expect(ae.type).equal('scrollEnd'); subscription2.unsubscribe(); done(); }); // simulate window scroll event - ee.emit('scroll', {foo: 'foo'}); + env.eventHandlers.scroll({foo: 'foo'}); }); it('resizeStart and resizeEnd should be triggered by the start/end of window resize', function (done) { - var subscription1 = subscribe('resizeStart', function (e, syntheticEvent) { + var subscription1 = subscribe('resizeStart', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('resizeStart'); + expect(ae.type).equal('resizeStart'); subscription1.unsubscribe(); }); - var subscription2 = subscribe('resizeEnd', function (e, syntheticEvent) { + var subscription2 = subscribe('resizeEnd', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('resizeEnd'); + expect(ae.type).equal('resizeEnd'); subscription2.unsubscribe(); done(); }); // simulate window scroll event - ee.emit('resize', {foo: 'foo'}); + env.eventHandlers.resize({foo: 'foo'}); }); it('scroll should be triggered by window scroll with scroll information', function (done) { // the first one subscription should get scroll info as well, because the second one requests - var subscription1 = subscribe('scroll', function (e, syntheticEvent) { + var subscription1 = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scroll'); - expect(syntheticEvent.scroll.top).equal(10); + expect(ae.type).equal('scroll'); + expect(ae.scroll.top).equal(10); subscription1.unsubscribe(); }, {enableScrollInfo: false}); // the second one request scroll info, which should dominate. - var subscription2 = subscribe('scroll', function (e, syntheticEvent) { + var subscription2 = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scroll'); - expect(syntheticEvent.scroll.top).equal(10); + expect(ae.type).equal('scroll'); + expect(ae.scroll.top).equal(10); subscription2.unsubscribe(); done(); }, {enableScrollInfo: true}); // simulate window scroll event - ee.emit('scroll', {foo: 'foo'}); + env.eventHandlers.scroll({foo: 'foo'}); }); it('resize should be triggered by window resize with resize information', function (done) { // the first one subscription should get resize info as well, because the second one requests - var subscription1 = subscribe('resize', function (e, syntheticEvent) { + var subscription1 = subscribe('resize', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('resize'); - expect(syntheticEvent.resize.width).equal(10); + expect(ae.type).equal('resize'); + expect(ae.resize.width).equal(20); subscription1.unsubscribe(); }, {enableResizeInfo: false}); - var subscription2 = subscribe('resize', function (e, syntheticEvent) { + var subscription2 = subscribe('resize', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('resize'); - expect(syntheticEvent.resize.width).equal(10); + expect(ae.type).equal('resize'); + expect(ae.resize.width).equal(20); subscription2.unsubscribe(); done(); }, {enableResizeInfo: true}); - // simulate window resize event - ee.emit('resize', {foo: 'foo'}); + // simulate window scroll event + env.eventHandlers.resize({foo: 'foo'}); }); - it('same event should be subscribed once', function (done) { + it('same main event should be subscribed once', function (done) { // for continuous events - var subscription1 = subscribe('scroll', function (e, syntheticEvent) { + var subscription1 = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scroll'); + expect(ae.type).equal('scroll'); subscription1.unsubscribe(); }); - var subscription2 = subscribe('scroll', function (e, syntheticEvent) { + var subscription2 = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scroll'); + expect(ae.type).equal('scroll'); subscription2.unsubscribe(); }); - // Actually, ee never listens to those ui events, such like 'scroll', 'resize', and 'visibilitychange'. - // It only listens to throttled events, like 'scroll:50', 'scroll:50:raf'. - // ee.listeners('scroll') will increase because I mocked window.addEventListener above to listen to // 'scroll' event. The number of listeners will stay at 1 if the same event is subscribed multiple times. - expect(ee.listeners('scroll').length).equal(1); + expect(ee.listeners('scroll:50').length).equal(2); + expect(globalVars.listeners['scroll:50']).to.be.ok(); // simulate window scroll event - ee.emit('scroll', {foo: 'foo'}); + env.eventHandlers.scroll({foo: 'foo'}); + + // remove scroll:50 listeners after unsubscibing + expect(globalVars.listeners['scroll:50']).equal(undefined); // for edge events - subscription1 = subscribe('scrollStart', function (e, syntheticEvent) { + subscription1 = subscribe('scrollStart', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scrollStart'); + expect(ae.type).equal('scrollStart'); subscription1.unsubscribe(); }); - subscription2 = subscribe('scrollStart', function (e, syntheticEvent) { + subscription2 = subscribe('scrollStart', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('scrollStart'); + expect(ae.type).equal('scrollStart'); subscription2.unsubscribe(); }); - expect(ee.listeners('scroll').length).equal(1); + expect(ee.listeners('scrollStart:50').length).equal(2); + expect(globalVars.listeners['scroll:50']).to.be.ok(); // simulate window scroll event - ee.emit('scroll', {foo: 'foo'}); + env.eventHandlers.scroll({foo: 'foo'}); + + // remove scroll:50 listeners after unsubscibing + expect(globalVars.listeners['scroll:50']).equal(undefined); - // for viewportchange event - subscription1 = subscribe('viewportchange', function (e, syntheticEvent) { + // for visibilitychange event + subscription1 = subscribe('visibilitychange', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('viewportchange'); + expect(ae.type).equal('visibilitychange'); subscription1.unsubscribe(); }); - subscription2 = subscribe('viewportchange', function (e, syntheticEvent) { + subscription2 = subscribe('visibilitychange', function (e, ae) { expect(e.foo).equal('foo'); - expect(syntheticEvent.type).equal('viewportchange'); + expect(ae.type).equal('visibilitychange'); subscription2.unsubscribe(); done(); }); - expect(ee.listeners('scroll').length).equal(1); - expect(ee.listeners('resize').length).equal(1); - expect(ee.listeners('visibilitychange').length).equal(1); + expect(ee.listeners('visibilitychange:0').length).equal(2); + expect(globalVars.listeners['visibilitychange:0']).to.be.ok(); // simulate window scroll event - ee.emit('visibilitychange', {foo: 'foo'}); + env.eventHandlers.visibilitychange({foo: 'foo'}); }); it('should not have fatal error if multiple unsubscibe happens', function (done) { - var subscription = subscribe('scroll', function (e, syntheticEvent) { + var subscription = subscribe('scroll', function (e, ae) { expect(e.foo).equal('foo'); subscription.unsubscribe(); subscription.unsubscribe(); + expect(globalVars.connections['scroll:50']).equal(0); done(); }); - ee.emit('scroll', {foo: 'foo'}); + + // simulate window scroll event + env.eventHandlers.scroll({foo: 'foo'}); }); }); }); diff --git a/tests/unit/unsubscribe.js b/tests/unit/unsubscribe.js deleted file mode 100644 index 3b21518..0000000 --- a/tests/unit/unsubscribe.js +++ /dev/null @@ -1,67 +0,0 @@ -/** - * Copyright 2015, Yahoo! Inc. - * Copyrights licensed under the New BSD License. See the accompanying LICENSE file for terms. - */ -/* global describe, it, before */ - -'use strict'; - -var ee = require('../../../src/eventEmitter').eventEmitter; - -GLOBAL.window = { - addEventListener: function (eventType, cb) { - ee.on(eventType, cb); - }, - removeEventListener: function (eventType, cb) { - ee.removeListener(eventType, cb); - }, - setTimeout: function (cb, wait) { - cb(); - } -}; -GLOBAL.document = { - addEventListener: function (eventType, cb) { - ee.on(eventType, cb); - }, - removeEventListener: function (eventType, cb) { - ee.removeListener(eventType, cb); - } -}; - -var expect = require('expect.js'); -var subscribe = require('../../../src/subscribe'); -var subscriptions = require('../../../src/eventEmitter').subscriptions; -var unsubscribe = require('../../../src/unsubscribe'); - -describe('unsubscribe', function () { - before(function () { - // unsubscribe all subscriptions, subscriptions may come from other tests - for (var i = subscriptions.length - 1; i >= 0; i--) { - subscriptions[i].unsubscribe(); - } - }); - - describe('#unsubscribe', function () { - it('should unsubscribe the event', function () { - function eventHandler() { - // empty function - } - - // make sure no subscriptions at beginning - expect(subscriptions.length).to.equal(0); - - // it works that the same eventHandler subscribes to the same eventType but with different options, - // but it doesn't make sense. So when unsubscribing, those 2 will be unsubscribe together. - subscribe('scroll', eventHandler); - subscribe('scroll', eventHandler, {throttleRate: 300}); - expect(subscriptions.length).to.equal(2); - expect(ee.listeners('scroll:50', true)).to.be.true; - expect(ee.listeners('scroll:300', true)).to.be.true; - - // will remove all instances with the same eventType and eventHandler - unsubscribe('scroll', eventHandler); - expect(subscriptions.length).to.equal(0); - expect(ee.listeners('scroll:50', true)).to.be.false; - }); - }); -});