| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | /** | 
					
						
							|  |  |  |  * @license | 
					
						
							| 
									
										
										
										
											2020-05-19 12:08:49 -07:00
										 |  |  |  * Copyright Google LLC All Rights Reserved. | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  |  * | 
					
						
							|  |  |  |  * Use of this source code is governed by an MIT-style license that can be | 
					
						
							|  |  |  |  * found in the LICENSE file at https://angular.io/license
 | 
					
						
							|  |  |  |  */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import {ifEnvSupports} from '../test-util'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | describe('element', function() { | 
					
						
							|  |  |  |   let button: HTMLButtonElement; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   beforeEach(function() { | 
					
						
							|  |  |  |     button = document.createElement('button'); | 
					
						
							|  |  |  |     document.body.appendChild(button); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |   afterEach(function() { | 
					
						
							|  |  |  |     document.body.removeChild(button); | 
					
						
							|  |  |  |   }); | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |   // https://github.com/angular/zone.js/issues/190
 | 
					
						
							|  |  |  |   it('should work when addEventListener / removeEventListener are called in the global context', | 
					
						
							|  |  |  |      function() { | 
					
						
							|  |  |  |        const clickEvent = document.createEvent('Event'); | 
					
						
							|  |  |  |        let callCount = 0; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        clickEvent.initEvent('click', true, true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        const listener = function(event: Event) { | 
					
						
							|  |  |  |          callCount++; | 
					
						
							|  |  |  |          expect(event).toBe(clickEvent); | 
					
						
							|  |  |  |        }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        // `this` would be null inside the method when `addEventListener` is called from strict mode
 | 
					
						
							|  |  |  |        // it would be `window`:
 | 
					
						
							|  |  |  |        // - when called from non strict-mode,
 | 
					
						
							|  |  |  |        // - when `window.addEventListener` is called explicitly.
 | 
					
						
							|  |  |  |        addEventListener('click', listener); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  |        expect(callCount).toEqual(1); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        removeEventListener('click', listener); | 
					
						
							|  |  |  |        button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  |        expect(callCount).toEqual(1); | 
					
						
							|  |  |  |      }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should work with addEventListener when called with a function listener', function() { | 
					
						
							|  |  |  |     const clickEvent = document.createEvent('Event'); | 
					
						
							|  |  |  |     clickEvent.initEvent('click', true, true); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |     button.addEventListener('click', function(event) { | 
					
						
							|  |  |  |       expect(event).toBe(clickEvent as any); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should not call microtasks early when an event is invoked', function(done) { | 
					
						
							|  |  |  |     let log = ''; | 
					
						
							|  |  |  |     button.addEventListener('click', () => { | 
					
						
							|  |  |  |       Zone.current.scheduleMicroTask('test', () => log += 'microtask;'); | 
					
						
							|  |  |  |       log += 'click;'; | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |     button.click(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(log).toEqual('click;'); | 
					
						
							|  |  |  |     done(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should call microtasks early when an event is invoked', function(done) { | 
					
						
							|  |  |  |     /* | 
					
						
							|  |  |  |      * In this test we escape the Zone using unpatched setTimeout. | 
					
						
							|  |  |  |      * This way the eventTask invoked from click will think it is the top most | 
					
						
							|  |  |  |      * task and eagerly drain the microtask queue. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * THIS IS THE WRONG BEHAVIOR! | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * But there is no easy way for the task to know if it is the top most task. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * Given that this can only arise when someone is emulating clicks on DOM in a synchronous | 
					
						
							|  |  |  |      * fashion we have few choices: | 
					
						
							|  |  |  |      * 1. Ignore as this is unlikely to be a problem outside of tests. | 
					
						
							|  |  |  |      * 2. Monkey patch the event methods to increment the _numberOfNestedTaskFrames and prevent | 
					
						
							|  |  |  |      *    eager drainage. | 
					
						
							|  |  |  |      * 3. Pay the cost of throwing an exception in event tasks and verifying that we are the | 
					
						
							|  |  |  |      *    top most frame. | 
					
						
							|  |  |  |      * | 
					
						
							|  |  |  |      * For now we are choosing to ignore it and assume that this arises in tests only. | 
					
						
							|  |  |  |      * As an added measure we make sure that all jasmine tests always run in a task. See: jasmine.ts | 
					
						
							|  |  |  |      */ | 
					
						
							|  |  |  |     (window as any)[(Zone as any).__symbol__('setTimeout')](() => { | 
					
						
							|  |  |  |       let log = ''; | 
					
						
							|  |  |  |       button.addEventListener('click', () => { | 
					
						
							|  |  |  |         Zone.current.scheduleMicroTask('test', () => log += 'microtask;'); | 
					
						
							|  |  |  |         log += 'click;'; | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |       button.click(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       expect(log).toEqual('click;microtask;'); | 
					
						
							|  |  |  |       done(); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should work with addEventListener when called with an EventListener-implementing listener', | 
					
						
							|  |  |  |      function() { | 
					
						
							|  |  |  |        const eventListener = { | 
					
						
							|  |  |  |          x: 5, | 
					
						
							|  |  |  |          handleEvent: function(event: Event) { | 
					
						
							|  |  |  |            // Test that context is preserved
 | 
					
						
							|  |  |  |            expect(this.x).toBe(5); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |            expect(event).toBe(clickEvent); | 
					
						
							|  |  |  |          } | 
					
						
							|  |  |  |        }; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        const clickEvent = document.createEvent('Event'); | 
					
						
							|  |  |  |        clickEvent.initEvent('click', true, true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        button.addEventListener('click', eventListener); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |        button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  |      }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should respect removeEventListener when called with a function listener', function() { | 
					
						
							|  |  |  |     let log = ''; | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |     const logFunction = function logFunction() { | 
					
						
							|  |  |  |       log += 'a'; | 
					
						
							|  |  |  |     }; | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     button.addEventListener('click', logFunction); | 
					
						
							|  |  |  |     button.addEventListener('focus', logFunction); | 
					
						
							|  |  |  |     button.click(); | 
					
						
							|  |  |  |     expect(log).toEqual('a'); | 
					
						
							|  |  |  |     const focusEvent = document.createEvent('Event'); | 
					
						
							|  |  |  |     focusEvent.initEvent('focus', true, true); | 
					
						
							|  |  |  |     button.dispatchEvent(focusEvent); | 
					
						
							|  |  |  |     expect(log).toEqual('aa'); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.removeEventListener('click', logFunction); | 
					
						
							|  |  |  |     button.click(); | 
					
						
							|  |  |  |     expect(log).toEqual('aa'); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should respect removeEventListener with an EventListener-implementing listener', function() { | 
					
						
							|  |  |  |     const eventListener = {x: 5, handleEvent: jasmine.createSpy('handleEvent')}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.addEventListener('click', eventListener); | 
					
						
							|  |  |  |     button.removeEventListener('click', eventListener); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.click(); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(eventListener.handleEvent).not.toHaveBeenCalled(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should have no effect while calling addEventListener without listener', function() { | 
					
						
							|  |  |  |     const onAddEventListenerSpy = jasmine.createSpy('addEventListener'); | 
					
						
							|  |  |  |     const eventListenerZone = | 
					
						
							|  |  |  |         Zone.current.fork({name: 'eventListenerZone', onScheduleTask: onAddEventListenerSpy}); | 
					
						
							|  |  |  |     expect(function() { | 
					
						
							|  |  |  |       eventListenerZone.run(function() { | 
					
						
							|  |  |  |         button.addEventListener('click', null as any); | 
					
						
							|  |  |  |         button.addEventListener('click', undefined as any); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }).not.toThrowError(); | 
					
						
							|  |  |  |     expect(onAddEventListenerSpy).not.toHaveBeenCalledWith(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should have no effect while calling removeEventListener without listener', function() { | 
					
						
							|  |  |  |     const onAddEventListenerSpy = jasmine.createSpy('removeEventListener'); | 
					
						
							|  |  |  |     const eventListenerZone = | 
					
						
							|  |  |  |         Zone.current.fork({name: 'eventListenerZone', onScheduleTask: onAddEventListenerSpy}); | 
					
						
							|  |  |  |     expect(function() { | 
					
						
							|  |  |  |       eventListenerZone.run(function() { | 
					
						
							|  |  |  |         button.removeEventListener('click', null as any); | 
					
						
							|  |  |  |         button.removeEventListener('click', undefined as any); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }).not.toThrowError(); | 
					
						
							|  |  |  |     expect(onAddEventListenerSpy).not.toHaveBeenCalledWith(); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should only add a listener once for a given set of arguments', function() { | 
					
						
							|  |  |  |     const log: string[] = []; | 
					
						
							|  |  |  |     const clickEvent = document.createEvent('Event'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |     function listener() { | 
					
						
							|  |  |  |       log.push('listener'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     clickEvent.initEvent('click', true, true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.addEventListener('click', listener); | 
					
						
							|  |  |  |     button.addEventListener('click', listener); | 
					
						
							|  |  |  |     button.addEventListener('click', listener); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  |     expect(log).toEqual(['listener']); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.removeEventListener('click', listener); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  |     expect(log).toEqual(['listener']); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should correctly handler capturing versus nonCapturing eventListeners', function() { | 
					
						
							|  |  |  |     const log: string[] = []; | 
					
						
							|  |  |  |     const clickEvent = document.createEvent('Event'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |     function capturingListener() { | 
					
						
							|  |  |  |       log.push('capturingListener'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |     function bubblingListener() { | 
					
						
							|  |  |  |       log.push('bubblingListener'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     clickEvent.initEvent('click', true, true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     document.body.addEventListener('click', capturingListener, true); | 
					
						
							|  |  |  |     document.body.addEventListener('click', bubblingListener); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(log).toEqual(['capturingListener', 'bubblingListener']); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   it('should correctly handler a listener that is both capturing and nonCapturing', function() { | 
					
						
							|  |  |  |     const log: string[] = []; | 
					
						
							|  |  |  |     const clickEvent = document.createEvent('Event'); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |     function listener() { | 
					
						
							|  |  |  |       log.push('listener'); | 
					
						
							|  |  |  |     } | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     clickEvent.initEvent('click', true, true); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     document.body.addEventListener('click', listener, true); | 
					
						
							|  |  |  |     document.body.addEventListener('click', listener); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     document.body.removeEventListener('click', listener, true); | 
					
						
							|  |  |  |     document.body.removeEventListener('click', listener); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     button.dispatchEvent(clickEvent); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     expect(log).toEqual(['listener', 'listener']); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('onclick', function() { | 
					
						
							|  |  |  |     function supportsOnClick() { | 
					
						
							|  |  |  |       const div = document.createElement('div'); | 
					
						
							|  |  |  |       const clickPropDesc = Object.getOwnPropertyDescriptor(div, 'onclick'); | 
					
						
							|  |  |  |       return !( | 
					
						
							|  |  |  |           EventTarget && div instanceof EventTarget && clickPropDesc && | 
					
						
							|  |  |  |           clickPropDesc.value === null); | 
					
						
							|  |  |  |     } | 
					
						
							|  |  |  |     (<any>supportsOnClick).message = 'Supports Element#onclick patching'; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     ifEnvSupports(supportsOnClick, function() { | 
					
						
							|  |  |  |       it('should spawn new child zones', function() { | 
					
						
							|  |  |  |         let run = false; | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |         button.onclick = function() { | 
					
						
							|  |  |  |           run = true; | 
					
						
							|  |  |  |         }; | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |         button.click(); | 
					
						
							|  |  |  |         expect(run).toBeTruthy(); | 
					
						
							|  |  |  |       }); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should only allow one onclick handler', function() { | 
					
						
							|  |  |  |       let log = ''; | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |       button.onclick = function() { | 
					
						
							|  |  |  |         log += 'a'; | 
					
						
							|  |  |  |       }; | 
					
						
							|  |  |  |       button.onclick = function() { | 
					
						
							|  |  |  |         log += 'b'; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |       button.click(); | 
					
						
							|  |  |  |       expect(log).toEqual('b'); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should handler removing onclick', function() { | 
					
						
							|  |  |  |       let log = ''; | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |       button.onclick = function() { | 
					
						
							|  |  |  |         log += 'a'; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  |       button.onclick = null as any; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       button.click(); | 
					
						
							|  |  |  |       expect(log).toEqual(''); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should be able to deregister the same event twice', function() { | 
					
						
							|  |  |  |       const listener = (event: Event) => {}; | 
					
						
							|  |  |  |       document.body.addEventListener('click', listener, false); | 
					
						
							|  |  |  |       document.body.removeEventListener('click', listener, false); | 
					
						
							|  |  |  |       document.body.removeEventListener('click', listener, false); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |   describe('onEvent default behavior', function() { | 
					
						
							|  |  |  |     let checkbox: HTMLInputElement; | 
					
						
							|  |  |  |     beforeEach(function() { | 
					
						
							|  |  |  |       checkbox = document.createElement('input'); | 
					
						
							|  |  |  |       checkbox.type = 'checkbox'; | 
					
						
							|  |  |  |       document.body.appendChild(checkbox); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |     afterEach(function() { | 
					
						
							|  |  |  |       document.body.removeChild(checkbox); | 
					
						
							|  |  |  |     }); | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |     it('should be possible to prevent default behavior by returning false', function() { | 
					
						
							| 
									
										
										
										
											2020-04-13 16:40:21 -07:00
										 |  |  |       checkbox.onclick = function() { | 
					
						
							|  |  |  |         return false; | 
					
						
							|  |  |  |       }; | 
					
						
							| 
									
										
										
										
											2019-06-01 00:56:07 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |       checkbox.click(); | 
					
						
							|  |  |  |       expect(checkbox.checked).toBe(false); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     it('should have no effect on default behavior when not returning anything', function() { | 
					
						
							|  |  |  |       checkbox.onclick = function() {}; | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       checkbox.click(); | 
					
						
							|  |  |  |       expect(checkbox.checked).toBe(true); | 
					
						
							|  |  |  |     }); | 
					
						
							|  |  |  |   }); | 
					
						
							|  |  |  | }); |