Update the license headers throughout the repository to reference Google LLC rather than Google Inc, for the required license headers. PR Close #37205
		
			
				
	
	
		
			339 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			339 lines
		
	
	
		
			10 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google LLC All Rights Reserved.
 | |
|  *
 | |
|  * 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);
 | |
|   });
 | |
| 
 | |
|   afterEach(function() {
 | |
|     document.body.removeChild(button);
 | |
|   });
 | |
| 
 | |
|   // 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);
 | |
| 
 | |
|     button.addEventListener('click', function(event) {
 | |
|       expect(event).toBe(clickEvent as any);
 | |
|     });
 | |
| 
 | |
|     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 = '';
 | |
|     const logFunction = function logFunction() {
 | |
|       log += 'a';
 | |
|     };
 | |
| 
 | |
|     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');
 | |
| 
 | |
|     function listener() {
 | |
|       log.push('listener');
 | |
|     }
 | |
| 
 | |
|     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');
 | |
| 
 | |
|     function capturingListener() {
 | |
|       log.push('capturingListener');
 | |
|     }
 | |
| 
 | |
|     function bubblingListener() {
 | |
|       log.push('bubblingListener');
 | |
|     }
 | |
| 
 | |
|     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');
 | |
| 
 | |
|     function listener() {
 | |
|       log.push('listener');
 | |
|     }
 | |
| 
 | |
|     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;
 | |
|         button.onclick = function() {
 | |
|           run = true;
 | |
|         };
 | |
| 
 | |
|         button.click();
 | |
|         expect(run).toBeTruthy();
 | |
|       });
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should only allow one onclick handler', function() {
 | |
|       let log = '';
 | |
|       button.onclick = function() {
 | |
|         log += 'a';
 | |
|       };
 | |
|       button.onclick = function() {
 | |
|         log += 'b';
 | |
|       };
 | |
| 
 | |
|       button.click();
 | |
|       expect(log).toEqual('b');
 | |
|     });
 | |
| 
 | |
| 
 | |
|     it('should handler removing onclick', function() {
 | |
|       let log = '';
 | |
|       button.onclick = function() {
 | |
|         log += 'a';
 | |
|       };
 | |
|       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);
 | |
|     });
 | |
| 
 | |
|     afterEach(function() {
 | |
|       document.body.removeChild(checkbox);
 | |
|     });
 | |
| 
 | |
|     it('should be possible to prevent default behavior by returning false', function() {
 | |
|       checkbox.onclick = function() {
 | |
|         return false;
 | |
|       };
 | |
| 
 | |
|       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);
 | |
|     });
 | |
|   });
 | |
| });
 |