/* Physical Device Testing Scenarios --------------------------------- - Keyboard should show up when tapping on a text input - Keyboard should show up when tapping on a label which surrounds a text input - Keyboard should stay up if focused text input is tapped again - Keyboard should go away when text input is focused, then tapped outside of input - Keyboard should hide when tapping the virtual keyboard's "Done" or down arrow, but tapping - Should be able to move an inputs text caret when its focused - Should be able to move an inputs text caret when its focused, and is wrapped with a label the input again will bring up the keyboard again - Options dialog should show when tapping on a select - Options dialog should show when tapping on a label which surrounds a select (not working in Android 2.3) - Tapping a button element should fire one click - Tapping an anchor element should fire one click - Tapping a checkbox should fire one click - Tapping a label which surrounds a checkbox should fire one click - Tapping a radio button should fire one click - Tapping a label which surrounds a radio button should fire one click - Moving an input[range] slider should work - Moving an input[range] slider when its in menu content of a should work - Tapping the track on an input[range] slider should move the knob to that location (not a default in iOS) - Tapping an input[file] should bring up the file dialog - After tapping an input[file] and closing the input file dialog, tap a different element and the file dialog should NOT show up - Element which is disabled should not be clicked - Holding a touchstart, and not moving, should fire the click no matter how long the hold - Holding a mousedown, and not moving, should fire the click no matter how long the hold - Holding touchstart, then moving a few pixels cancels the click - Holding mousedown, then moving a few pixels cancels the click - Touchstart should set and remove the activated css class - Mousedown should set and remove the activated css class - Holding touchstart, then moving a few pixels removes the activated css class - Holding mousedown, then moving a few pixels removes the activated css class - An element or one of its parents with data-tap-disabled attribute should still click, but w/ a delay - ALL THE ABOVE, BUT NOW WITH NG-CLICK ON THE INPUT! - Tapping a div with an click event added should fire one click - Tapping an img with an click event added should fire one click - Can scroll when target is a text input, but it does not have focus - Can scroll when the target is a text input and it already has focus - Can hold a text input and move its text caret/cursor - Can scroll when the target is a label - Does not change focus 300ms after when tapping an input and the keyboard shows up - The blinking cursor says in the input 300ms after the tap - Can hit the keyboard's "next" button to change focus to the next input - When flicking down a page, and a target was an input, it shouldn't focus and jank scrolling - Do not show text caret of a focused input while scrolling, no matter what the target is - After scrolling has come to a halt, previously focused input should be focused again - Keyboard should stay up while scrolling if an input was focused - Keyboard should not go away after scrolling stops and it focuses back on the previous focused input - Should not create clones for tap inputs to hide cursor, like for checkboxes, radio, range, select - Focus on an input, flick up, let go, and during animation flick down, and clone should go away - While inputs are actively scrolling you should not be able to change focus - If you touchstart on a text input, then scrolland touchend, it should not bring up the keyboard - If you touchstart on a label wrapping a text input, then scroll and touchend, it should not bring up the keyboard - When focused in a text input, be able to get out when tapping a checkbox - Can scroll when the target is a select element (touch events only) - Can scroll when the target is a label which wraps select element - Can open the select options on touch when not scrolling - Can open the select options mouse click when target is a select - Can open the select options when target is a label wrapping a select Tested on: ---------------------------- - iOS 6.1 iPad 3 - iOS 7.0 iPhone 4 - iOS 7.0 iPhone 5 - iOS 7.1 iPhone 5 - iOS 7.1 Simulator - Android 2.3 HTC Incredible - Android 2.3 Samsung Galaxy S - Android 4.0 HTC Incredible - Android 4.2 Nexus 4 - Android 4.3 Samsung S3 - Android 4.4 Motorola Moto G - Android 4.4 Nexus 5 - OSX Chrome - OSX Chrome (emulated touch screen) - OSX Firefox */ window.console.log = function(){}; describe('Ionic Tap', function() { var deregisterTap; beforeEach(function() { window._setTimeout = window.setTimeout; window.setTimeout = function(){}; _activeElement = null; // the element which has focus deregisterTap = ionic.tap.register(document.createElement('div')); ionic.scroll = { isScrolling: false }; }); afterEach(function(){ window.setTimeout = window._setTimeout; deregisterTap(); }); it('Should trigger a labels child inputs click, and should not stop the labels end event so Android allows text editing', function() { var e = { type: 'touchstart', target: { tagName: 'LABEL', dispatchEvent: function() { e.target.dispatchedEvent = true; }, focus: function() { e.target.focused = true; }, control: { tagName: 'INPUT', dispatchEvent: function() { e.target.control.dispatchedEvent = true; }, focus: function() { e.target.control.focused = true; } } }, stopPropagation: function() { e.stoppedPropagation = true; }, preventDefault: function() { e.preventedDefault = true; } }; tapClick(e); expect( e.target.dispatchedEvent ).toBeUndefined(); expect( e.target.focused ).toBeUndefined(); expect( e.target.control.dispatchedEvent ).toBeDefined(); expect( e.target.control.focused ).toBeDefined(); expect( e.stoppedPropagation ).toBeUndefined(); expect( e.preventedDefault ).toBeUndefined(); }); it('Should trigger a click for an element w/out a wrapping label', function() { var e = { type: 'touchstart', target: { tagName: 'INPUT', dispatchEvent: function() { e.target.dispatchedEvent = true; }, focus: function() { e.target.focused = true; } }, stopPropagation: function() { e.stoppedPropagation = true; }, preventDefault: function() { e.preventedDefault = true; } }; tapClick(e); expect( e.target.dispatchedEvent ).toBeDefined(); expect( e.target.focused ).toBeDefined(); expect( e.stoppedPropagation ).toBeUndefined(); expect( e.preventedDefault ).toBeUndefined(); }); it('Should not trigger a click if tapPointerMoved has moved', function() { var e = { type: 'touchstart', target: { tagName: 'INPUT', dispatchEvent: function() { e.target.dispatchedEvent = true; }, focus: function() { e.target.focused = true; } }, stopPropagation: function() { e.stoppedPropagation = true; }, preventDefault: function() { e.preventedDefault = true; } }; tapPointerMoved = true; expect( tapClick(e) ).toEqual(false); }); it('Should trigger click on mouseup and when nearby mousedown happened', function() { var e = { type: 'mousedown', clientX: 100, clientY: 100, target: { tagName: 'INPUT', dispatchEvent: function() { e.target.dispatchedEvent = true; }, focus: function() { e.target.focused = true; } }, stopPropagation: function() { e.stoppedPropagation = true; }, preventDefault: function() { e.preventedDefault = true; } }; expect( e.target.dispatchedEvent ).toBeUndefined(); tapMouseDown({clientX: 101, clientY: 101}); tapMouseUp(e); expect( e.target.dispatchedEvent ).toBeDefined(); }); it('Should not trigger click on mouseup because mousedown coordinates too far away', function() { var e = { clientX: 100, clientY: 100, target: { tagName: 'INPUT', dispatchEvent: function() { e.target.dispatchedEvent = true; }, focus: function() { e.target.focused = true; } }, stopPropagation: function() { e.stoppedPropagation = true; }, preventDefault: function() { e.preventedDefault = true; } }; expect( e.target.dispatchedEvent ).toBeUndefined(); tapMouseDown({clientX: 201, clientY: 101}); tapMouseUp(e); expect( e.target.dispatchedEvent ).toBeUndefined(); }); it('Should set tapHasPointerMoved=false on tapTouchStart', function() { tapPointerMoved = null; tapTouchStart({ preventDefault:function(){} }); expect( tapPointerMoved ).toEqual(false); }); it('Should not preventDefault on text input that already has focus and is iOS', function() { // prevent the default so iOS doesn't auto scroll to the input ionic.Platform.setPlatform('ios'); var label = document.createElement('label'); var textarea = document.createElement('textarea'); label.appendChild(textarea); var e = { target: label, preventDefault: function() { e.preventedDefault = true; } }; tapActiveEle = textarea; tapTouchStart(e); expect( e.preventedDefault ).toBeUndefined(); }); it('Should preventDefault on text input that does not have focus and is iOS', function() { // prevent the default so iOS doesn't auto scroll to the input ionic.Platform.setPlatform('ios'); var label = document.createElement('label'); var textarea = document.createElement('textarea'); label.appendChild(textarea); var e = { target: label, preventDefault: function() { e.preventedDefault = true; } }; tapTouchStart(e); expect( e.preventedDefault ).toEqual(true); }); it('Should not preventDefault on text input target thats not iOS', function() { // do not prevent default on touchend of a text input or else you cannot move the text caret ionic.Platform.setPlatform('android'); var label = document.createElement('label'); var textarea = document.createElement('textarea'); label.appendChild(textarea); var e = { target: label, preventDefault: function() { e.preventedDefault = true; } }; tapTouchStart(e); expect( e.preventedDefault ).toBeUndefined(); }); it('Should set tapPointerMoved=false on tapTouchCancel', function() { tapPointerMoved = true; tapTouchCancel(); expect( tapPointerMoved ).toEqual(false); }); it('Should set tapHasPointerMoved=true on tapTouchMove', function() { tapPointerMoved = null; tapTouchStart({ clientX: 100, clientY: 100, preventDefault:function(){} }); expect( tapPointerMoved ).toEqual(false); tapTouchMove({ clientX: 200, clientY: 100 }); expect( tapPointerMoved ).toEqual(true); }); it('Should set tapHasPointerMoved=false on tapMouseDown', function() { tapPointerMoved = null; tapMouseDown({}); expect( tapPointerMoved ).toEqual(false); }); it('Should set tapPointerMoved=false on tapMouseUp', function() { tapPointerMoved = true; tapMouseUp({ target: {} }); expect( tapPointerMoved ).toEqual(false); }); it('Should set tapHasPointerMoved=true on tapMouseMove', function() { tapPointerMoved = null; tapMouseDown({ clientX: 100, clientY: 100 }); expect( tapPointerMoved ).toEqual(false); tapMouseMove({ clientX: 200, clientY: 100 }); expect( tapPointerMoved ).toEqual(true); }); it('Should stop event on mouseup if touch is enabled', function() { tapEnabledTouchEvents = true; var e = { stopPropagation: function() { this.stoppedPropagation = true; }, preventDefault: function() { this.preventedDefault = true; } } tapMouseUp(e); expect( e.stoppedPropagation ).toEqual(true); expect( e.preventedDefault ).toEqual(true); }); it('Should not stop event on mouseup if the target was a select element', function() { tapEnabledTouchEvents = false; e = { target: document.createElement('select'), stopPropagation: function() { this.stoppedPropagation = true; }, preventDefault: function() { this.preventedDefault = true; } } expect( tapMouseUp(e) ).toEqual(false); expect( e.stoppedPropagation ).toBeUndefined(); expect( e.preventedDefault ).toBeUndefined(); }); it('Should not stop event on mouseup if touch is not enabled', function() { tapEnabledTouchEvents = false; e = { target: document.createElement('button'), stopPropagation: function() { this.stoppedPropagation = true; }, preventDefault: function() { this.preventedDefault = true; } } tapMouseUp(e); expect( e.stoppedPropagation ).toBeUndefined(); expect( e.preventedDefault ).toBeUndefined(); }); it('Should trigger click on touchend and nearby touchstart happened', function() { var e = { type: 'touchend', clientX: 101, clientY: 101, target: { tagName: 'INPUT', dispatchEvent: function() { e.target.dispatchedEvent = true; }, focus: function() { e.target.focused = true; } }, stopPropagation: function() { e.stoppedPropagation = true; }, preventDefault: function() { e.preventedDefault = true; } }; tapTouchStart({clientX: 100, clientY: 100, preventDefault:function(){}}); tapTouchEnd(e); expect( e.target.dispatchedEvent ).toBeDefined(); }); it('Should not trigger click on touchend because touchstart coordinates too far away', function() { var e = { type: 'touchstart', clientX: 100, clientY: 100, target: { tagName: 'INPUT', dispatchEvent: function() { e.target.dispatchedEvent = true; }, focus: function() { e.target.focused = true; } }, stopPropagation: function() { e.stoppedPropagation = true; }, preventDefault: function() { e.preventedDefault = true; } }; expect( e.target.dispatchedEvent ).toBeUndefined(); tapTouchStart({clientX: 200, clientY: 100, preventDefault:function(){}}); tapTouchEnd(e); expect( e.target.dispatchedEvent ).toBeUndefined(); }); it('Should tapEnabledTouchEvents because of touchstart', function() { tapEnabledTouchEvents = false; tapTouchStart({preventDefault:function(){}}); tapEnabledTouchEvents = true; }); it('Should cancel click on touchcancel', function() { tapTouchCancel(); expect(tapPointerMoved).toEqual(false); }); it('Should cancel click when touchmove coordinates goes too far from touchstart coordinates', function() { var e = { clientX: 100, clientY: 100, preventDefault:function(){} }; tapTouchStart(e); expect( tapTouchMove({ clientX: 102, clientY: 100 }) ).toBeUndefined(); expect( tapTouchMove({ clientX: 105, clientY: 100 }) ).toBeUndefined(); expect( tapTouchMove({ clientX: 200, clientY: 100 }) ).toEqual(false); }); it('Should cancel click when touchend coordinates are too far from touchstart coordinates', function() { var e = { clientX: 100, clientY: 100, dispatchEvent: function(){ this.dispatchedEvent = true; }, preventDefault:function(){} }; tapTouchStart(e); tapTouchEnd({ clientX: 200, clientY: 100 }); expect( e.dispatchedEvent ).toBeUndefined(); }); it('Should cancel click when mousemove coordinates goes too far from mousedown coordinates', function() { var e = { clientX: 100, clientY: 100 }; tapMouseDown(e); expect( tapMouseMove({ clientX: 102, clientY: 100 }) ).toBeUndefined(); expect( tapMouseMove({ clientX: 105, clientY: 100 }) ).toBeUndefined(); expect( tapMouseMove({ clientX: 200, clientY: 100 }) ).toEqual(false); }); it('Should cancel click when mouseup coordinates are too far from mousedown coordinates', function() { var e1 = { target: document.createElement('button'), clientX: 100, clientY: 100, dispatchEvent: function(){ this.dispatchedEvent = true; } }; tapMouseDown(e1); var e2 = { target: document.createElement('button'), clientX: 100, clientY: 100, dispatchEvent: function(){ this.dispatchedEvent = true; } }; tapMouseUp(e2); expect( e2.dispatchedEvent ).toBeUndefined(); }); it('Should do nothing if mousedown is a custom event from ionic tap', function() { var e = { isTapHandled: false, isIonicTap: true }; tapMouseDown(e); expect( e.isTapHandled ).toEqual(false); }); it('Should tapClick with touchend and fire immediately', function() { var e = { target: { tagName: 'button', dispatchEvent: function(){ this.dispatchedEvent = true; } } }; tapClick(e); expect(e.target.dispatchedEvent).toEqual(true); }); it('Should tapHasPointerMoved false if are null', function() { expect( tapHasPointerMoved(null) ).toEqual(false); }); it('Should tapPointerStart false if are null', function() { tapPointerStart = null; expect( tapHasPointerMoved(null) ).toEqual(false); }); it('Should tapPointerStart false if are null', function() { var e = {}; tapPointerStart = {x:0, y:0}; expect( tapHasPointerMoved(e) ).toEqual(false); }); it('Should tapHasPointerMoved true if greater than or equal to release tolerance', function() { tapPointerStart = { x: 100, y: 100 }; var s = tapHasPointerMoved({ clientX: 111, clientY: 100 }); expect(s).toEqual(true); s = tapHasPointerMoved({ clientX: 89, clientY: 100 }); expect(s).toEqual(true); s = tapHasPointerMoved({ clientX: 100, clientY: 109 }); expect(s).toEqual(true); s = tapHasPointerMoved({ clientX: 100, clientY: 91 }); expect(s).toEqual(true); s = tapHasPointerMoved({ clientX: 100, clientY: 200 }); expect(s).toEqual(true); }); it('Should tapHasPointerMoved false if less than release tolerance', function() { tapPointerStart = { x: 100, y: 100 }; var s = tapHasPointerMoved({ clientX: 100, clientY: 100 }); expect(s).toEqual(false); s = tapHasPointerMoved({ clientX: 104, clientY: 100 }); expect(s).toEqual(false); s = tapHasPointerMoved({ clientX: 96, clientY: 100 }); expect(s).toEqual(false); s = tapHasPointerMoved({ clientX: 100, clientY: 102 }); expect(s).toEqual(false); s = tapHasPointerMoved({ clientX: 100, clientY: 98 }); expect(s).toEqual(false); }); it('Should not be tapHasPointerMoved if 0 coordinates', function() { var e = { clientX: 0, clientY: 0 }; var s = tapHasPointerMoved(e, { clientX: 100, clientY: 100 }); expect(s).toEqual(false); }); it('Should get coordinates from page mouse event', function() { var e = { pageX: 77, pageY: 77 }; var c = getPointerCoordinates(e); expect(c).toEqual({x:77, y: 77}); }); it('Should get coordinates from client mouse event', function() { var e = { clientX: 77, clientY: 77 }; var c = getPointerCoordinates(e); expect(c).toEqual({x:77, y: 77}); }); it('Should get coordinates from changedTouches touches', function() { var e = { touches: [{ clientX: 99, clientY: 99 }], changedTouches: [{ clientX: 88, clientY: 88 }] }; var c = getPointerCoordinates(e); expect(c).toEqual({x:88, y: 88}); }); it('Should get coordinates from page touches', function() { var e = { touches: [{ pageX: 99, pageY: 99 }] }; var c = getPointerCoordinates(e); expect(c).toEqual({x:99, y: 99}); }); it('Should get coordinates from client touches', function() { var e = { touches: [{ clientX: 99, clientY: 99 }] }; var c = getPointerCoordinates(e); expect(c).toEqual({x:99, y: 99}); }); it('Should get 0 coordinates', function() { var e = {}; var c = getPointerCoordinates(e); expect(c).toEqual({x:0, y: 0}); }); it('Should not tapClick for disabled elements', function() { // Disabled elements should not be tapped var targetEle = document.createElement('input'); targetEle.disabled = true; var e = { target: targetEle }; expect( tapClick(e) ).toEqual(false); }); it('Should ionic.tap.requiresNativeClick for invalid element', function() { expect( ionic.tap.requiresNativeClick( null ) ).toEqual(true); }); it('Should ionic.tap.requiresNativeClick for input.disabled', function() { var ele = document.createElement('input'); ele.disabled = true; expect( ionic.tap.requiresNativeClick( ele ) ).toEqual(true); }); it('Should ionic.tap.requiresNativeClick for input[range]', function() { var ele = document.createElement('input'); ele.type = 'range'; expect( ionic.tap.requiresNativeClick( ele ) ).toEqual(true); }); it('Should ionic.tap.requiresNativeClick for input[file]', function() { var ele = document.createElement('input'); ele.type = 'file'; expect( ionic.tap.requiresNativeClick( ele ) ).toEqual(true); }); it('Should ionic.tap.requiresNativeClick for video element', function() { var ele = document.createElement('video'); expect( ionic.tap.requiresNativeClick( ele ) ).toEqual(true); }); it('Should ionic.tap.requiresNativeClick for object element', function() { var ele = document.createElement('object'); expect( ionic.tap.requiresNativeClick( ele ) ).toEqual(true); }); it('Should not ionic.tap.requiresNativeClick for common inputs', function() { var inputTypes = ['text', 'email', 'search', 'tel', 'number', 'date', 'month', 'password', null, undefined, '']; for(var x=0; x