diff --git a/src/jqLite.js b/src/jqLite.js index 0ed760801b73..07715e94cdcd 100644 --- a/src/jqLite.js +++ b/src/jqLite.js @@ -274,18 +274,22 @@ function jqLiteOff(element, type, fn, unsupported) { if (!type) { for (type in events) { if (type !== '$destroy') { - removeEventListenerFn(element, type, events[type]); + removeEventListenerFn(element, type, handle); } delete events[type]; } } else { forEach(type.split(' '), function(type) { - if (isUndefined(fn)) { - removeEventListenerFn(element, type, events[type]); - delete events[type]; - } else { - arrayRemove(events[type] || [], fn); + if (isDefined(fn)) { + var listenerFns = events[type]; + arrayRemove(listenerFns || [], fn); + if (listenerFns && listenerFns.length > 0) { + return; + } } + + removeEventListenerFn(element, type, handle); + delete events[type]; }); } } diff --git a/test/jqLiteSpec.js b/test/jqLiteSpec.js index f52bd4058246..a6ae18d35fa8 100644 --- a/test/jqLiteSpec.js +++ b/test/jqLiteSpec.js @@ -1400,6 +1400,94 @@ describe('jqLite', function() { expect(extraSpy).toHaveBeenCalledOnce(); }); + + describe('native listener deregistration', function() { + + it('should deregister the native listener when all jqLite listeners for given type are gone ' + + 'after off("eventName", listener) call', function() { + var aElem = jqLite(a); + var addEventListenerSpy = spyOn(aElem[0], 'addEventListener').andCallThrough(); + var removeEventListenerSpy = spyOn(aElem[0], 'removeEventListener').andCallThrough(); + var nativeListenerFn; + + var jqLiteListener = function() {}; + aElem.on('click', jqLiteListener); + + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1]; + expect(removeEventListenerSpy).not.toHaveBeenCalled(); + + aElem.off('click', jqLiteListener); + expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn, false); + }); + + + it('should deregister the native listener when all jqLite listeners for given type are gone ' + + 'after off("eventName") call', function() { + var aElem = jqLite(a); + var addEventListenerSpy = spyOn(aElem[0], 'addEventListener').andCallThrough(); + var removeEventListenerSpy = spyOn(aElem[0], 'removeEventListener').andCallThrough(); + var nativeListenerFn; + + aElem.on('click', function() {}); + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1]; + expect(removeEventListenerSpy).not.toHaveBeenCalled(); + + aElem.off('click'); + expect(removeEventListenerSpy).toHaveBeenCalledOnceWith('click', nativeListenerFn, false); + }); + + + it('should deregister the native listener when all jqLite listeners for given type are gone ' + + 'after off("eventName1 eventName2") call', function() { + var aElem = jqLite(a); + var addEventListenerSpy = spyOn(aElem[0], 'addEventListener').andCallThrough(); + var removeEventListenerSpy = spyOn(aElem[0], 'removeEventListener').andCallThrough(); + var nativeListenerFn; + + aElem.on('click', function() {}); + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1]; + addEventListenerSpy.reset(); + + aElem.on('dblclick', function() {}); + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn, false); + + expect(removeEventListenerSpy).not.toHaveBeenCalled(); + + aElem.off('click dblclick'); + + expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn, false); + expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn, false); + expect(removeEventListenerSpy.callCount).toBe(2); + }); + + + it('should deregister the native listener when all jqLite listeners for given type are gone ' + + 'after off() call', function() { + var aElem = jqLite(a); + var addEventListenerSpy = spyOn(aElem[0], 'addEventListener').andCallThrough(); + var removeEventListenerSpy = spyOn(aElem[0], 'removeEventListener').andCallThrough(); + var nativeListenerFn; + + aElem.on('click', function() {}); + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('click', jasmine.any(Function), false); + nativeListenerFn = addEventListenerSpy.mostRecentCall.args[1]; + addEventListenerSpy.reset(); + + aElem.on('dblclick', function() {}); + expect(addEventListenerSpy).toHaveBeenCalledOnceWith('dblclick', nativeListenerFn, false); + + aElem.off(); + + expect(removeEventListenerSpy).toHaveBeenCalledWith('click', nativeListenerFn, false); + expect(removeEventListenerSpy).toHaveBeenCalledWith('dblclick', nativeListenerFn, false); + expect(removeEventListenerSpy.callCount).toBe(2); + }); + }); + + // Only run this test for jqLite and not normal jQuery if ( _jqLiteMode ) { it('should throw an error if a selector is passed', function () {