From 707c65d5a228b44ab3aea2fad95516fe6c57169a Mon Sep 17 00:00:00 2001 From: Braden Shepherdson Date: Fri, 8 Feb 2013 13:39:23 -0500 Subject: feat(ngMobile): add ngMobile module with mobile-specific ngClick Add a new module ngMobile, with mobile/touch-specific directives. Add ngClick, which overrides the default ngClick. This ngClick uses touch events, which are much faster on mobile. On desktop browsers, ngClick responds to click events, so it can be used for portable sites. --- test/ngMobile/directive/ngClickSpec.js | 295 +++++++++++++++++++++++++++++++++ 1 file changed, 295 insertions(+) create mode 100644 test/ngMobile/directive/ngClickSpec.js (limited to 'test/ngMobile') diff --git a/test/ngMobile/directive/ngClickSpec.js b/test/ngMobile/directive/ngClickSpec.js new file mode 100644 index 00000000..2f42662d --- /dev/null +++ b/test/ngMobile/directive/ngClickSpec.js @@ -0,0 +1,295 @@ +'use strict'; + +describe('ngClick (mobile)', function() { + var element, time, orig_now; + + // TODO(braden): Once we have other touch-friendly browsers on CI, allow them here. + // Currently Firefox and IE refuse to fire touch events. + var chrome = /chrome/.test(navigator.userAgent.toLowerCase()); + if (!chrome) { + return; + } + + function mockTime() { + return time; + } + + + beforeEach(function() { + module('ngMobile'); + orig_now = Date.now; + time = 0; + Date.now = mockTime; + }); + + afterEach(function() { + dealoc(element); + Date.now = orig_now; + }); + + + 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); + })); + + + it('should pass event object', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); + + browserTrigger(element, 'touchstart'); + browserTrigger(element, 'touchend'); + expect($rootScope.event).toBeDefined(); + })); + + + 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); + + time = 10; + browserTrigger(element, 'touchstart', [], 10, 10); + + time = 900; + browserTrigger(element, 'touchend', [], 10, 10); + + 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(); + + browserTrigger(element, 'touchstart', [], 10, 10); + browserTrigger(element, 'touchend', [], 400, 400); + + expect($rootScope.tapped).toBeUndefined(); + })); + + + it('should not click if a touchmove comes before touchend', inject(function($rootScope, $compile, $rootElement) { + element = $compile('
')($rootScope); + $rootElement.append(element); + $rootScope.$digest(); + + expect($rootScope.tapped).toBeUndefined(); + + browserTrigger(element, 'touchstart', [], 10, 10); + browserTrigger(element, 'touchmove'); + browserTrigger(element, 'touchend', [], 400, 400); + + expect($rootScope.tapped).toBeUndefined(); + })); + + + describe('the clickbuster', function() { + var element1, element2; + + beforeEach(inject(function($rootElement, $document) { + $document.find('body').append($rootElement); + })); + + afterEach(inject(function($document) { + $document.find('body').html(''); + })); + + + it('should cancel the following click event', inject(function($rootScope, $compile, $rootElement, $document) { + 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', [], 10, 10); + + time = 50; + browserTrigger(element, 'touchend', [], 10, 10); + + expect($rootScope.count).toBe(1); + + time = 100; + browserTrigger(element, 'click', [], 10, 10); + + expect($rootScope.count).toBe(1); + })); + + + 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; + + $rootScope.$digest(); + + expect($rootScope.count1).toBe(0); + expect($rootScope.count2).toBe(0); + + time = 10; + browserTrigger(element1, 'touchstart', [], 10, 10); + + time = 50; + browserTrigger(element1, 'touchend', [], 10, 10); + + expect($rootScope.count1).toBe(1); + + time = 100; + browserTrigger(element2, 'click', [], 10, 10); + + expect($rootScope.count1).toBe(1); + expect($rootScope.count2).toBe(0); + })); + + + it('should not cancel clicks on distant elements', 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; + + $rootScope.$digest(); + + expect($rootScope.count1).toBe(0); + expect($rootScope.count2).toBe(0); + + time = 10; + browserTrigger(element1, 'touchstart', [], 10, 10); + + time = 50; + browserTrigger(element1, 'touchend', [], 10, 10); + + expect($rootScope.count1).toBe(1); + + time = 90; + browserTrigger(element1, 'click', [], 10, 10); + + expect($rootScope.count1).toBe(1); + + time = 100; + browserTrigger(element1, 'touchstart', [], 10, 10); + + time = 130; + browserTrigger(element1, 'touchend', [], 10, 10); + + expect($rootScope.count1).toBe(2); + + // Click on other element that should go through. + time = 150; + browserTrigger(element2, 'touchstart', [], 100, 120); + browserTrigger(element2, 'touchend', [], 100, 120); + browserTrigger(element2, 'click', [], 100, 120); + + expect($rootScope.count2).toBe(1); + + // Click event for the element that should be busted. + time = 200; + browserTrigger(element1, 'click', [], 10, 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.count = 0; + + $rootScope.$digest(); + + expect($rootScope.count).toBe(0); + + time = 10; + browserTrigger(element1, 'touchstart', [], 10, 10); + + time = 50; + browserTrigger(element1, 'touchend', [], 10, 10); + expect($rootScope.count).toBe(1); + + time = 2700; + browserTrigger(element1, 'click', [], 10, 10); + + expect($rootScope.count).toBe(2); + })); + + + it('should not cancel clicks that come long after', inject(function($rootScope, $compile) { + element1 = $compile('
')($rootScope); + + $rootScope.count = 0; + + $rootScope.$digest(); + + expect($rootScope.count).toBe(0); + + time = 10; + browserTrigger(element1, 'touchstart', [], 10, 10); + + time = 50; + browserTrigger(element1, 'touchend', [], 10, 10); + + expect($rootScope.count).toBe(1); + + time = 2700; + browserTrigger(element1, 'click', [], 10, 10); + + expect($rootScope.count).toBe(2); + })); + }); + + + 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(); + + browserTrigger(element, 'click'); + expect($rootScope.tapped).toEqual(true); + })); + + + it('should pass event object', inject(function($rootScope, $compile) { + element = $compile('
')($rootScope); + $rootScope.$digest(); + + browserTrigger(element, 'click'); + expect($rootScope.event).toBeDefined(); + })); + }); +}); -- cgit v1.2.3