diff --git a/src/ngTouch/directive/ngClick.js b/src/ngTouch/directive/ngClick.js index f352c252f443..d4c24bd28b88 100644 --- a/src/ngTouch/directive/ngClick.js +++ b/src/ngTouch/directive/ngClick.js @@ -7,8 +7,17 @@ /** * @ngdoc directive * @name ngClick + * @deprecated * * @description + *
+ * **DEPRECATION NOTICE**: Beginning with Angular 1.5, this directive is deprecated and by default **disabled**. + * The directive will receive no further support and might be removed from future releases. + * If you need the directive, you can enable it with the {@link ngTouch.$touchProvider $touchProvider#ngClickOverrideEnabled} + * function. We also recommend that you migrate to [FastClick](https://github.com/ftlabs/fastclick). + * To learn more about the 300ms delay, this [Telerik article](http://developer.telerik.com/featured/300-ms-click-delay-ios-8/) + * gives a good overview. + *
* A more powerful replacement for the default ngClick designed to be used on touchscreen * devices. Most mobile browsers wait about 300ms after a tap-and-release before sending * the click event. This version handles them immediately, and then prevents the @@ -40,15 +49,7 @@ */ -ngTouch.config(['$provide', function($provide) { - $provide.decorator('ngClickDirective', ['$delegate', function($delegate) { - // drop the default ngClick directive - $delegate.shift(); - return $delegate; - }]); -}]); - -ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', +var ngTouchClickDirectiveFactory = ['$parse', '$timeout', '$rootElement', function($parse, $timeout, $rootElement) { var TAP_DURATION = 750; // Shorter than 750ms is a tap, longer is a taphold or drag. var MOVE_TOLERANCE = 12; // 12px seems to work in most mobile browsers. @@ -292,5 +293,5 @@ ngTouch.directive('ngClick', ['$parse', '$timeout', '$rootElement', }); }; -}]); +}]; diff --git a/src/ngTouch/touch.js b/src/ngTouch/touch.js index 8191ff0b753a..09966953884d 100644 --- a/src/ngTouch/touch.js +++ b/src/ngTouch/touch.js @@ -1,5 +1,8 @@ 'use strict'; +/* global ngTouchClickDirectiveFactory: false, + */ + /** * @ngdoc module * @name ngTouch @@ -22,6 +25,104 @@ /* global -ngTouch */ var ngTouch = angular.module('ngTouch', []); +ngTouch.provider('$touch', $TouchProvider); + function nodeName_(element) { return angular.lowercase(element.nodeName || (element[0] && element[0].nodeName)); } + +/** + * @ngdoc provider + * @name $touchProvider + * + * @description + * The `$touchProvider` allows enabling / disabling {@link ngTouch.ngClick ngTouch's ngClick directive}. + */ +$TouchProvider.$inject = ['$provide', '$compileProvider']; +function $TouchProvider($provide, $compileProvider) { + + /** + * @ngdoc method + * @name $touchProvider#ngClickOverrideEnabled + * + * @param {boolean=} enabled update the ngClickOverrideEnabled state if provided, otherwise just return the + * current ngClickOverrideEnabled state + * @returns {*} current value if used as getter or itself (chaining) if used as setter + * + * @kind function + * + * @description + * Call this method to enable/disable {@link ngTouch.ngClick ngTouch's ngClick directive}. If enabled, + * the default ngClick directive will be replaced by a version that eliminates the 300ms delay for + * click events on browser for touch-devices. + * + * The default is `false`. + * + */ + var ngClickOverrideEnabled = false; + var ngClickDirectiveAdded = false; + this.ngClickOverrideEnabled = function(enabled) { + if (angular.isDefined(enabled)) { + + if (enabled && !ngClickDirectiveAdded) { + ngClickDirectiveAdded = true; + + // Use this to identify the correct directive in the delegate + ngTouchClickDirectiveFactory.$$moduleName = 'ngTouch'; + $compileProvider.directive('ngClick', ngTouchClickDirectiveFactory); + + $provide.decorator('ngClickDirective', ['$delegate', function($delegate) { + if (ngClickOverrideEnabled) { + // drop the default ngClick directive + $delegate.shift(); + } else { + // drop the ngTouch ngClick directive if the override has been re-disabled (because + // we cannot de-register added directives) + var i = $delegate.length - 1; + while (i >= 0) { + if ($delegate[i].$$moduleName === 'ngTouch') { + $delegate.splice(i, 1); + break; + } + i--; + } + } + + return $delegate; + }]); + } + + ngClickOverrideEnabled = enabled; + return this; + } + + return ngClickOverrideEnabled; + }; + + /** + * @ngdoc service + * @name $touch + * @kind object + * + * @description + * Provides the {@link ngTouch.$touch#ngClickOverrideEnabled `ngClickOverrideEnabled`} method. + * + */ + this.$get = function() { + return { + /** + * @ngdoc method + * @name $touch#ngClickOverrideEnabled + * + * @returns {*} current value of `ngClickOverrideEnabled` set in the {@link ngTouch.$touchProvider $touchProvider}, + * i.e. if {@link ngTouch.ngClick ngTouch's ngClick} directive is enabled. + * + * @kind function + */ + ngClickOverrideEnabled: function() { + return ngClickOverrideEnabled; + } + }; + }; + +} diff --git a/test/ngTouch/directive/ngClickSpec.js b/test/ngTouch/directive/ngClickSpec.js index c9765b4c8df9..dc453cd83345 100644 --- a/test/ngTouch/directive/ngClickSpec.js +++ b/test/ngTouch/directive/ngClickSpec.js @@ -15,197 +15,170 @@ describe('ngClick (touch)', function() { } - beforeEach(function() { - module('ngTouch'); - orig_now = Date.now; - time = 0; - Date.now = mockTime; - }); - - afterEach(function() { - dealoc(element); - Date.now = orig_now; - }); + describe('config', function() { + beforeEach(module('ngTouch')); + it('should expose ngClickOverrideEnabled in the $touchProvider', function() { + var _$touchProvider; - it('should get called on a tap', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect($rootScope.tapped).toBeUndefined(); - - browserTrigger(element, 'touchstart'); - browserTrigger(element, 'touchend'); - expect($rootScope.tapped).toEqual(true); - })); - + module(function($touchProvider) { + _$touchProvider = $touchProvider; + }); - it('should pass event object', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$digest(); + inject(function() { + expect(_$touchProvider.ngClickOverrideEnabled).toEqual(jasmine.any(Function)); + }); + }); - browserTrigger(element, 'touchstart'); - browserTrigger(element, 'touchend'); - expect($rootScope.event).toBeDefined(); - })); - if (window.jQuery) { - it('should not unwrap a jQuery-wrapped event object on click', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$digest(); + it('should return "false" for ngClickOverrideEnabled by default', function() { + var enabled; - browserTrigger(element, 'click', { - keys: [], - x: 10, - y: 10 + module(function($touchProvider) { + enabled = $touchProvider.ngClickOverrideEnabled(); }); - expect($rootScope.event.originalEvent).toBeDefined(); - expect($rootScope.event.originalEvent.clientX).toBe(10); - expect($rootScope.event.originalEvent.clientY).toBe(10); - })); - it('should not unwrap a jQuery-wrapped event object on touchstart/touchend', - inject(function($rootScope, $compile, $rootElement) { - element = $compile('
')($rootScope); - $rootElement.append(element); - $rootScope.$digest(); + inject(function() { + expect(enabled).toBe(false); + }); + }); - browserTrigger(element, 'touchstart'); - browserTrigger(element, 'touchend'); - expect($rootScope.event.originalEvent).toBeDefined(); - })); - } + it('should not apply the ngClick override directive by default', function() { + inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect($rootScope.tapped).toBeUndefined(); + browserTrigger(element, 'touchstart'); + browserTrigger(element, 'touchend'); + expect($rootScope.tapped).toBeUndefined(); + }); + }); + }); - it('should not click if the touch is held too long', inject(function($rootScope, $compile, $rootElement) { - element = $compile('
')($rootScope); - $rootElement.append(element); - $rootScope.count = 0; - $rootScope.$digest(); + describe('interaction with custom ngClick directives', function() { - expect($rootScope.count).toBe(0); + it('should not remove other ngClick directives when removing ngTouch ngClick in the decorator', function() { + // Add another ngClick before ngTouch + module(function($compileProvider) { + $compileProvider.directive('ngClick', function() { + return {}; + }); + }); - time = 10; - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); + module('ngTouch'); - time = 900; - browserTrigger(element, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); + module(function($touchProvider) { + $touchProvider.ngClickOverrideEnabled(true); + $touchProvider.ngClickOverrideEnabled(false); + }); - expect($rootScope.count).toBe(0); - })); + inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect($rootScope.tapped).toBeUndefined(); + browserTrigger(element, 'touchstart'); + browserTrigger(element, 'touchend'); + expect($rootScope.tapped).toBeUndefined(); + }); + }); - it('should not click if the touchend is too far away', inject(function($rootScope, $compile, $rootElement) { - element = $compile('
')($rootScope); - $rootElement.append(element); - $rootScope.$digest(); + }); - expect($rootScope.tapped).toBeUndefined(); + describe('directive', function() { - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - browserTrigger(element, 'touchend',{ - keys: [], - x: 400, - y: 400 + beforeEach(function() { + module('ngTouch'); + module(function($touchProvider) { + $touchProvider.ngClickOverrideEnabled(true); + }); + orig_now = Date.now; + time = 0; + Date.now = mockTime; }); - expect($rootScope.tapped).toBeUndefined(); - })); - + afterEach(function() { + dealoc(element); + Date.now = orig_now; + }); - it('should not prevent click if a touchmove comes before touchend', inject(function($rootScope, $compile, $rootElement) { - element = $compile('
')($rootScope); - $rootElement.append(element); - $rootScope.$digest(); + it('should not apply the ngClick override directive if ngClickOverrideEnabled has been set to false again', function() { + module(function($touchProvider) { + // beforeEach calls this with "true" + $touchProvider.ngClickOverrideEnabled(false); + }); - expect($rootScope.tapped).toBeUndefined(); + inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect($rootScope.tapped).toBeUndefined(); - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - browserTrigger(element, 'touchmove'); - browserTrigger(element, 'touchend',{ - keys: [], - x: 15, - y: 15 + browserTrigger(element, 'touchstart'); + browserTrigger(element, 'touchend'); + expect($rootScope.tapped).toBeUndefined(); + }); }); - expect($rootScope.tapped).toEqual(true); - })); - it('should add the CSS class while the element is held down, and then remove it', inject(function($rootScope, $compile, $rootElement) { - element = $compile('
')($rootScope); - $rootElement.append(element); - $rootScope.$digest(); - expect($rootScope.tapped).toBeUndefined(); + it('should get called on a tap', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect($rootScope.tapped).toBeUndefined(); - var CSS_CLASS = 'ng-click-active'; + browserTrigger(element, 'touchstart'); + browserTrigger(element, 'touchend'); + expect($rootScope.tapped).toEqual(true); + })); - expect(element.hasClass(CSS_CLASS)).toBe(false); - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - expect(element.hasClass(CSS_CLASS)).toBe(true); - browserTrigger(element, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); - expect(element.hasClass(CSS_CLASS)).toBe(false); - expect($rootScope.tapped).toBe(true); - })); - it('should click when target element is an SVG', inject( - function($rootScope, $compile, $rootElement) { - element = $compile('')($rootScope); - $rootElement.append(element); + it('should pass event object', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); $rootScope.$digest(); browserTrigger(element, 'touchstart'); browserTrigger(element, 'touchend'); - browserTrigger(element, 'click', {x:1, y:1}); + expect($rootScope.event).toBeDefined(); + })); - expect($rootScope.tapped).toEqual(true); - })); + if (window.jQuery) { + it('should not unwrap a jQuery-wrapped event object on click', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); - describe('the clickbuster', function() { - var element1, element2; + browserTrigger(element, 'click', { + keys: [], + x: 10, + y: 10 + }); + expect($rootScope.event.originalEvent).toBeDefined(); + expect($rootScope.event.originalEvent.clientX).toBe(10); + expect($rootScope.event.originalEvent.clientY).toBe(10); + })); - beforeEach(inject(function($rootElement, $document) { - $document.find('body').append($rootElement); - })); + it('should not unwrap a jQuery-wrapped event object on touchstart/touchend', + inject(function($rootScope, $compile, $rootElement) { + element = $compile('
')($rootScope); + $rootElement.append(element); + $rootScope.$digest(); - afterEach(inject(function($document) { - $document.find('body').empty(); - })); + browserTrigger(element, 'touchstart'); + browserTrigger(element, 'touchend'); + + expect($rootScope.event.originalEvent).toBeDefined(); + })); + } - it('should cancel the following click event', inject(function($rootScope, $compile, $rootElement, $document) { + it('should not click if the touch is held too long', inject(function($rootScope, $compile, $rootElement) { element = $compile('
')($rootScope); $rootElement.append(element); - $rootScope.count = 0; $rootScope.$digest(); expect($rootScope.count).toBe(0); - // Fire touchstart at 10ms, touchend at 50ms, the click at 300ms. time = 10; browserTrigger(element, 'touchstart',{ keys: [], @@ -213,443 +186,565 @@ describe('ngClick (touch)', function() { y: 10 }); - time = 50; + time = 900; browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); - expect($rootScope.count).toBe(1); + expect($rootScope.count).toBe(0); + })); + + + it('should not click if the touchend is too far away', inject(function($rootScope, $compile, $rootElement) { + element = $compile('
')($rootScope); + $rootElement.append(element); + $rootScope.$digest(); + + expect($rootScope.tapped).toBeUndefined(); - time = 100; - browserTrigger(element, 'click',{ + browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 400, + y: 400 + }); - expect($rootScope.count).toBe(1); + expect($rootScope.tapped).toBeUndefined(); })); - it('should cancel the following click event even when the element has changed', inject( - function($rootScope, $compile, $rootElement) { - $rootElement.append( - '
x
' + - '
y
' - ); - $compile($rootElement)($rootScope); - - element1 = $rootElement.find('div').eq(0); - element2 = $rootElement.find('div').eq(1); - - $rootScope.count1 = 0; - $rootScope.count2 = 0; - + it('should not prevent click if a touchmove comes before touchend', inject(function($rootScope, $compile, $rootElement) { + element = $compile('
')($rootScope); + $rootElement.append(element); $rootScope.$digest(); - expect($rootScope.count1).toBe(0); - expect($rootScope.count2).toBe(0); + expect($rootScope.tapped).toBeUndefined(); - time = 10; - browserTrigger(element1, 'touchstart',{ + browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); + browserTrigger(element, 'touchmove'); + browserTrigger(element, 'touchend',{ + keys: [], + x: 15, + y: 15 + }); + + expect($rootScope.tapped).toEqual(true); + })); + + it('should add the CSS class while the element is held down, and then remove it', inject(function($rootScope, $compile, $rootElement) { + element = $compile('
')($rootScope); + $rootElement.append(element); + $rootScope.$digest(); + expect($rootScope.tapped).toBeUndefined(); + + var CSS_CLASS = 'ng-click-active'; - time = 50; - browserTrigger(element1, 'touchend',{ + expect(element.hasClass(CSS_CLASS)).toBe(false); + browserTrigger(element, 'touchstart',{ keys: [], x: 10, y: 10 }); - - expect($rootScope.count1).toBe(1); - - time = 100; - browserTrigger(element2, 'click',{ + expect(element.hasClass(CSS_CLASS)).toBe(true); + browserTrigger(element, 'touchend',{ keys: [], x: 10, y: 10 }); + expect(element.hasClass(CSS_CLASS)).toBe(false); + expect($rootScope.tapped).toBe(true); + })); - expect($rootScope.count1).toBe(1); - expect($rootScope.count2).toBe(0); + it('should click when target element is an SVG', inject( + function($rootScope, $compile, $rootElement) { + element = $compile('')($rootScope); + $rootElement.append(element); + $rootScope.$digest(); + + browserTrigger(element, 'touchstart'); + browserTrigger(element, 'touchend'); + browserTrigger(element, 'click', {x:1, y:1}); + + expect($rootScope.tapped).toEqual(true); })); + describe('the clickbuster', function() { + var element1, element2; - it('should not cancel clicks on distant elements', inject(function($rootScope, $compile, $rootElement) { - $rootElement.append( - '
x
' + - '
y
' - ); - $compile($rootElement)($rootScope); + beforeEach(inject(function($rootElement, $document) { + $document.find('body').append($rootElement); + })); - element1 = $rootElement.find('div').eq(0); - element2 = $rootElement.find('div').eq(1); + afterEach(inject(function($document) { + $document.find('body').empty(); + })); - $rootScope.count1 = 0; - $rootScope.count2 = 0; - $rootScope.$digest(); + it('should cancel the following click event', inject(function($rootScope, $compile, $rootElement, $document) { + element = $compile('
')($rootScope); + $rootElement.append(element); - expect($rootScope.count1).toBe(0); - expect($rootScope.count2).toBe(0); + $rootScope.count = 0; + $rootScope.$digest(); - time = 10; - browserTrigger(element1, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); + expect($rootScope.count).toBe(0); - time = 50; - browserTrigger(element1, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); + // Fire touchstart at 10ms, touchend at 50ms, the click at 300ms. + time = 10; + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); - expect($rootScope.count1).toBe(1); + time = 50; + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); - time = 90; - // Verify that it is blurred so we don't get soft-keyboard - element1[0].blur = jasmine.createSpy('blur'); - browserTrigger(element1, 'click',{ - keys: [], - x: 10, - y: 10 - }); - expect(element1[0].blur).toHaveBeenCalled(); + expect($rootScope.count).toBe(1); - expect($rootScope.count1).toBe(1); + time = 100; + browserTrigger(element, 'click',{ + keys: [], + x: 10, + y: 10 + }); - time = 100; - browserTrigger(element1, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); + expect($rootScope.count).toBe(1); + })); - time = 130; - browserTrigger(element1, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); - expect($rootScope.count1).toBe(2); + it('should cancel the following click event even when the element has changed', inject( + function($rootScope, $compile, $rootElement) { + $rootElement.append( + '
x
' + + '
y
' + ); + $compile($rootElement)($rootScope); - // Click on other element that should go through. - time = 150; - browserTrigger(element2, 'touchstart',{ - keys: [], - x: 100, - y: 120 - }); - browserTrigger(element2, 'touchend',{ - keys: [], - x: 100, - y: 120 - }); - browserTrigger(element2, 'click',{ - keys: [], - x: 100, - y: 120 - }); + element1 = $rootElement.find('div').eq(0); + element2 = $rootElement.find('div').eq(1); - expect($rootScope.count2).toBe(1); + $rootScope.count1 = 0; + $rootScope.count2 = 0; - // Click event for the element that should be busted. - time = 200; - browserTrigger(element1, 'click',{ - keys: [], - x: 10, - y: 10 - }); + $rootScope.$digest(); - expect($rootScope.count1).toBe(2); - expect($rootScope.count2).toBe(1); - })); + expect($rootScope.count1).toBe(0); + expect($rootScope.count2).toBe(0); + time = 10; + browserTrigger(element1, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + + time = 50; + browserTrigger(element1, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); - it('should not cancel clicks that come long after', inject(function($rootScope, $compile) { - element1 = $compile('
')($rootScope); + expect($rootScope.count1).toBe(1); - $rootScope.count = 0; + time = 100; + browserTrigger(element2, 'click',{ + keys: [], + x: 10, + y: 10 + }); - $rootScope.$digest(); + expect($rootScope.count1).toBe(1); + expect($rootScope.count2).toBe(0); + })); - expect($rootScope.count).toBe(0); - time = 10; - browserTrigger(element1, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); + it('should not cancel clicks on distant elements', inject(function($rootScope, $compile, $rootElement) { + $rootElement.append( + '
x
' + + '
y
' + ); + $compile($rootElement)($rootScope); - time = 50; - browserTrigger(element1, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); - expect($rootScope.count).toBe(1); + element1 = $rootElement.find('div').eq(0); + element2 = $rootElement.find('div').eq(1); - time = 2700; - browserTrigger(element1, 'click',{ - keys: [], - x: 10, - y: 10 - }); + $rootScope.count1 = 0; + $rootScope.count2 = 0; - expect($rootScope.count).toBe(2); - })); + $rootScope.$digest(); + expect($rootScope.count1).toBe(0); + expect($rootScope.count2).toBe(0); - describe('when clicking on a label immediately following a touch event', function() { - var touch = function(element, x, y) { time = 10; - browserTrigger(element, 'touchstart',{ + browserTrigger(element1, 'touchstart',{ keys: [], - x: x, - y: y + x: 10, + y: 10 }); time = 50; - browserTrigger(element, 'touchend',{ + browserTrigger(element1, 'touchend',{ keys: [], - x: x, - y: y + x: 10, + y: 10 }); - }; - var click = function(element, x, y) { - browserTrigger(element, 'click',{ + expect($rootScope.count1).toBe(1); + + time = 90; + // Verify that it is blurred so we don't get soft-keyboard + element1[0].blur = jasmine.createSpy('blur'); + browserTrigger(element1, 'click',{ + keys: [], + x: 10, + y: 10 + }); + expect(element1[0].blur).toHaveBeenCalled(); + + expect($rootScope.count1).toBe(1); + + time = 100; + browserTrigger(element1, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + + time = 130; + browserTrigger(element1, 'touchend',{ keys: [], - x: x, - y: y + x: 10, + y: 10 }); - }; - var $rootScope; - var container, otherElement, input, label; - beforeEach(inject(function(_$rootScope_, $compile, $rootElement) { - $rootScope = _$rootScope_; - var container = $compile('
' + - '' + - '
')($rootScope); - $rootElement.append(container); - otherElement = container.children()[0]; - input = container.children()[1]; - label = container.children()[2]; + expect($rootScope.count1).toBe(2); + + // Click on other element that should go through. + time = 150; + browserTrigger(element2, 'touchstart',{ + keys: [], + x: 100, + y: 120 + }); + browserTrigger(element2, 'touchend',{ + keys: [], + x: 100, + y: 120 + }); + browserTrigger(element2, 'click',{ + keys: [], + x: 100, + y: 120 + }); + + expect($rootScope.count2).toBe(1); + + // Click event for the element that should be busted. + time = 200; + browserTrigger(element1, 'click',{ + keys: [], + x: 10, + y: 10 + }); + + expect($rootScope.count1).toBe(2); + expect($rootScope.count2).toBe(1); + })); + + + it('should not cancel clicks that come long after', inject(function($rootScope, $compile) { + element1 = $compile('
')($rootScope); - $rootScope.selection = 'initial'; + $rootScope.count = 0; $rootScope.$digest(); + + expect($rootScope.count).toBe(0); + + time = 10; + browserTrigger(element1, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + + time = 50; + browserTrigger(element1, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); + expect($rootScope.count).toBe(1); + + time = 2700; + browserTrigger(element1, 'click',{ + keys: [], + x: 10, + y: 10 + }); + + expect($rootScope.count).toBe(2); })); - afterEach(function() { - dealoc(label); - dealoc(input); - dealoc(otherElement); - dealoc(container); - }); + describe('when clicking on a label immediately following a touch event', function() { + var touch = function(element, x, y) { + time = 10; + browserTrigger(element, 'touchstart',{ + keys: [], + x: x, + y: y + }); + + time = 50; + browserTrigger(element, 'touchend',{ + keys: [], + x: x, + y: y + }); + }; + + var click = function(element, x, y) { + browserTrigger(element, 'click',{ + keys: [], + x: x, + y: y + }); + }; + + var $rootScope; + var container, otherElement, input, label; + beforeEach(inject(function(_$rootScope_, $compile, $rootElement) { + $rootScope = _$rootScope_; + var container = $compile('
' + + '' + + '
')($rootScope); + $rootElement.append(container); + otherElement = container.children()[0]; + input = container.children()[1]; + label = container.children()[2]; + + $rootScope.selection = 'initial'; + + $rootScope.$digest(); + })); + + + afterEach(function() { + dealoc(label); + dealoc(input); + dealoc(otherElement); + dealoc(container); + }); - it('should not cancel input clicks with (0,0) coordinates', function() { - touch(otherElement, 100, 100); + it('should not cancel input clicks with (0,0) coordinates', function() { + touch(otherElement, 100, 100); - time = 500; - click(label, 10, 10); - click(input, 0, 0); + time = 500; + click(label, 10, 10); + click(input, 0, 0); - expect($rootScope.selection).toBe('radio1'); - }); + expect($rootScope.selection).toBe('radio1'); + }); - it('should not cancel input clicks with negative coordinates', function() { - touch(otherElement, 100, 100); + it('should not cancel input clicks with negative coordinates', function() { + touch(otherElement, 100, 100); - time = 500; - click(label, 10, 10); - click(input, -1, -1); + time = 500; + click(label, 10, 10); + click(input, -1, -1); - expect($rootScope.selection).toBe('radio1'); - }); + expect($rootScope.selection).toBe('radio1'); + }); - it('should not cancel input clicks with positive coordinates identical to label click', function() { - touch(otherElement, 100, 100); + it('should not cancel input clicks with positive coordinates identical to label click', function() { + touch(otherElement, 100, 100); - time = 500; - click(label, 10, 10); - click(input, 10, 10); + time = 500; + click(label, 10, 10); + click(input, 10, 10); - expect($rootScope.selection).toBe('radio1'); - }); + expect($rootScope.selection).toBe('radio1'); + }); - it('should cancel input clicks with positive coordinates different than label click', function() { - touch(otherElement, 100, 100); + it('should cancel input clicks with positive coordinates different than label click', function() { + touch(otherElement, 100, 100); - time = 500; - click(label, 10, 10); - click(input, 11, 11); + time = 500; + click(label, 10, 10); + click(input, 11, 11); - expect($rootScope.selection).toBe('initial'); - }); + expect($rootScope.selection).toBe('initial'); + }); - it('should blur the other element on click', function() { - var blurSpy = spyOn(otherElement, 'blur'); - touch(otherElement, 10, 10); + it('should blur the other element on click', function() { + var blurSpy = spyOn(otherElement, 'blur'); + touch(otherElement, 10, 10); - time = 500; - click(label, 10, 10); + time = 500; + click(label, 10, 10); - expect(blurSpy).toHaveBeenCalled(); + expect(blurSpy).toHaveBeenCalled(); + }); }); }); - }); - describe('click fallback', function() { + describe('click fallback', function() { - it('should treat a click as a tap on desktop', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$digest(); - expect($rootScope.tapped).toBeFalsy(); + it('should treat a click as a tap on desktop', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); + expect($rootScope.tapped).toBeFalsy(); - browserTrigger(element, 'click'); - expect($rootScope.tapped).toEqual(true); - })); + browserTrigger(element, 'click'); + expect($rootScope.tapped).toEqual(true); + })); - it('should pass event object', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.$digest(); + it('should pass event object', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); - browserTrigger(element, 'click'); - expect($rootScope.event).toBeDefined(); - })); - }); + browserTrigger(element, 'click'); + expect($rootScope.event).toBeDefined(); + })); + }); - describe('disabled state', function() { - it('should not trigger click if ngDisabled is true', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.disabled = true; - $rootScope.$digest(); + describe('disabled state', function() { + it('should not trigger click if ngDisabled is true', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.disabled = true; + $rootScope.$digest(); - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - browserTrigger(element, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); - expect($rootScope.event).toBeUndefined(); - })); - it('should trigger click if ngDisabled is false', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); - $rootScope.disabled = false; - $rootScope.$digest(); + expect($rootScope.event).toBeUndefined(); + })); + it('should trigger click if ngDisabled is false', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.disabled = false; + $rootScope.$digest(); - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - browserTrigger(element, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); - expect($rootScope.event).toBeDefined(); - })); - it('should not trigger click if regular disabled is true', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); + expect($rootScope.event).toBeDefined(); + })); + it('should not trigger click if regular disabled is true', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - browserTrigger(element, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); - expect($rootScope.event).toBeUndefined(); - })); - it('should not trigger click if regular disabled is present', inject(function($rootScope, $compile) { - element = $compile('')($rootScope); + expect($rootScope.event).toBeUndefined(); + })); + it('should not trigger click if regular disabled is present', inject(function($rootScope, $compile) { + element = $compile('')($rootScope); - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - browserTrigger(element, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); - expect($rootScope.event).toBeUndefined(); - })); - it('should trigger click if regular disabled is not present', inject(function($rootScope, $compile) { - element = $compile('
')($rootScope); + expect($rootScope.event).toBeUndefined(); + })); + it('should trigger click if regular disabled is not present', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - browserTrigger(element, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); - expect($rootScope.event).toBeDefined(); - })); - }); + expect($rootScope.event).toBeDefined(); + })); + }); - describe('the normal click event', function() { - it('should be capturable by other handlers', inject(function($rootScope, $compile) { - var called = false; + describe('the normal click event', function() { + it('should be capturable by other handlers', inject(function($rootScope, $compile) { + var called = false; - element = $compile('
')($rootScope); + element = $compile('
')($rootScope); - element.on('click', function() { - called = true; - }); + element.on('click', function() { + called = true; + }); - browserTrigger(element, 'touchstart',{ - keys: [], - x: 10, - y: 10 - }); - browserTrigger(element, 'touchend',{ - keys: [], - x: 10, - y: 10 - }); + browserTrigger(element, 'touchstart',{ + keys: [], + x: 10, + y: 10 + }); + browserTrigger(element, 'touchend',{ + keys: [], + x: 10, + y: 10 + }); - expect(called).toEqual(true); - })); + expect(called).toEqual(true); + })); + }); }); });