375 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			375 lines
		
	
	
		
			15 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
/**
 | 
						|
 * @license
 | 
						|
 * Copyright Google Inc. 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 {ɵgetDOM as getDOM} from '@angular/common';
 | 
						|
import {NgZone} from '@angular/core/src/zone/ng_zone';
 | 
						|
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
 | 
						|
import {DomEventsPlugin} from '@angular/platform-browser/src/dom/events/dom_events';
 | 
						|
import {EventManager, EventManagerPlugin} from '@angular/platform-browser/src/dom/events/event_manager';
 | 
						|
import {createMouseEvent, el} from '../../../testing/src/browser_util';
 | 
						|
 | 
						|
(function() {
 | 
						|
  if (isNode) return;
 | 
						|
  let domEventPlugin: DomEventsPlugin;
 | 
						|
  let doc: any;
 | 
						|
  let zone: NgZone;
 | 
						|
 | 
						|
  describe('EventManager', () => {
 | 
						|
    beforeEach(() => {
 | 
						|
      doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
 | 
						|
      zone = new NgZone({});
 | 
						|
      domEventPlugin = new DomEventsPlugin(doc);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should delegate event bindings to plugins that are passed in from the most generic one to the most specific one',
 | 
						|
       () => {
 | 
						|
         const element = el('<div></div>');
 | 
						|
         const handler = (e: any /** TODO #9100 */) => e;
 | 
						|
         const plugin = new FakeEventManagerPlugin(doc, ['click']);
 | 
						|
         const manager = new EventManager([domEventPlugin, plugin], new FakeNgZone());
 | 
						|
         manager.addEventListener(element, 'click', handler);
 | 
						|
         expect(plugin.eventHandler['click']).toBe(handler);
 | 
						|
       });
 | 
						|
 | 
						|
    it('should delegate event bindings to the first plugin supporting the event', () => {
 | 
						|
      const element = el('<div></div>');
 | 
						|
      const clickHandler = (e: any /** TODO #9100 */) => e;
 | 
						|
      const dblClickHandler = (e: any /** TODO #9100 */) => e;
 | 
						|
      const plugin1 = new FakeEventManagerPlugin(doc, ['dblclick']);
 | 
						|
      const plugin2 = new FakeEventManagerPlugin(doc, ['click', 'dblclick']);
 | 
						|
      const manager = new EventManager([plugin2, plugin1], new FakeNgZone());
 | 
						|
      manager.addEventListener(element, 'click', clickHandler);
 | 
						|
      manager.addEventListener(element, 'dblclick', dblClickHandler);
 | 
						|
      expect(plugin2.eventHandler['click']).toBe(clickHandler);
 | 
						|
      expect(plugin1.eventHandler['dblclick']).toBe(dblClickHandler);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should throw when no plugin can handle the event', () => {
 | 
						|
      const element = el('<div></div>');
 | 
						|
      const plugin = new FakeEventManagerPlugin(doc, ['dblclick']);
 | 
						|
      const manager = new EventManager([plugin], new FakeNgZone());
 | 
						|
      expect(() => manager.addEventListener(element, 'click', null !))
 | 
						|
          .toThrowError('No event manager plugin found for event click');
 | 
						|
    });
 | 
						|
 | 
						|
    it('events are caught when fired from a child', () => {
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      // Workaround for https://bugs.webkit.org/show_bug.cgi?id=122755
 | 
						|
      doc.body.appendChild(element);
 | 
						|
 | 
						|
      const child = element.firstChild as Element;
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvent: any /** TODO #9100 */ = null;
 | 
						|
      const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
      manager.addEventListener(element, 'click', handler);
 | 
						|
      getDOM().dispatchEvent(child, dispatchedEvent);
 | 
						|
 | 
						|
      expect(receivedEvent).toBe(dispatchedEvent);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should add and remove global event listeners', () => {
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvent: any /** TODO #9100 */ = null;
 | 
						|
      const handler = (e: any /** TODO #9100 */) => { receivedEvent = e; };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
 | 
						|
      const remover = manager.addGlobalEventListener('document', 'click', handler);
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvent).toBe(dispatchedEvent);
 | 
						|
 | 
						|
      receivedEvent = null;
 | 
						|
      remover();
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvent).toBe(null);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should keep zone when addEventListener', () => {
 | 
						|
      const Zone = (window as any)['Zone'];
 | 
						|
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvent: any /** TODO #9100 */ = null;
 | 
						|
      let receivedZone: any = null;
 | 
						|
      const handler = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvent = e;
 | 
						|
        receivedZone = Zone.current;
 | 
						|
      };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
 | 
						|
      let remover: any = null;
 | 
						|
      Zone.root.run(() => { remover = manager.addEventListener(element, 'click', handler); });
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvent).toBe(dispatchedEvent);
 | 
						|
      expect(receivedZone.name).toBe(Zone.root.name);
 | 
						|
 | 
						|
      receivedEvent = null;
 | 
						|
      remover && remover();
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvent).toBe(null);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should keep zone when addEventListener multiple times', () => {
 | 
						|
      const Zone = (window as any)['Zone'];
 | 
						|
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvents: any[] /** TODO #9100 */ = [];
 | 
						|
      let receivedZones: any[] = [];
 | 
						|
      const handler1 = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
      };
 | 
						|
      const handler2 = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
      };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
 | 
						|
      let remover1: any = null;
 | 
						|
      let remover2: any = null;
 | 
						|
      Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
 | 
						|
      Zone.root.fork({name: 'test'}).run(() => {
 | 
						|
        remover2 = manager.addEventListener(element, 'click', handler2);
 | 
						|
      });
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
 | 
						|
      expect(receivedZones).toEqual([Zone.root.name, 'test']);
 | 
						|
 | 
						|
      receivedEvents = [];
 | 
						|
      remover1 && remover1();
 | 
						|
      remover2 && remover2();
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([]);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should support event.stopImmediatePropagation', () => {
 | 
						|
      const Zone = (window as any)['Zone'];
 | 
						|
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvents: any[] /** TODO #9100 */ = [];
 | 
						|
      let receivedZones: any[] = [];
 | 
						|
      const handler1 = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
        e.stopImmediatePropagation();
 | 
						|
      };
 | 
						|
      const handler2 = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
      };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
 | 
						|
      let remover1: any = null;
 | 
						|
      let remover2: any = null;
 | 
						|
      Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
 | 
						|
      Zone.root.fork({name: 'test'}).run(() => {
 | 
						|
        remover2 = manager.addEventListener(element, 'click', handler2);
 | 
						|
      });
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([dispatchedEvent]);
 | 
						|
      expect(receivedZones).toEqual([Zone.root.name]);
 | 
						|
 | 
						|
      receivedEvents = [];
 | 
						|
      remover1 && remover1();
 | 
						|
      remover2 && remover2();
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([]);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should handle event correctly when one handler remove itself ', () => {
 | 
						|
      const Zone = (window as any)['Zone'];
 | 
						|
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvents: any[] /** TODO #9100 */ = [];
 | 
						|
      let receivedZones: any[] = [];
 | 
						|
      let remover1: any = null;
 | 
						|
      let remover2: any = null;
 | 
						|
      const handler1 = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
        remover1 && remover1();
 | 
						|
      };
 | 
						|
      const handler2 = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
      };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
 | 
						|
      Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
 | 
						|
      Zone.root.fork({name: 'test'}).run(() => {
 | 
						|
        remover2 = manager.addEventListener(element, 'click', handler2);
 | 
						|
      });
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
 | 
						|
      expect(receivedZones).toEqual([Zone.root.name, 'test']);
 | 
						|
 | 
						|
      receivedEvents = [];
 | 
						|
      remover1 && remover1();
 | 
						|
      remover2 && remover2();
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([]);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should only add same callback once when addEventListener', () => {
 | 
						|
      const Zone = (window as any)['Zone'];
 | 
						|
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvents: any[] /** TODO #9100 */ = [];
 | 
						|
      let receivedZones: any[] = [];
 | 
						|
      const handler = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
      };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
 | 
						|
      let remover1: any = null;
 | 
						|
      let remover2: any = null;
 | 
						|
      Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler); });
 | 
						|
      Zone.root.fork({name: 'test'}).run(() => {
 | 
						|
        remover2 = manager.addEventListener(element, 'click', handler);
 | 
						|
      });
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([dispatchedEvent]);
 | 
						|
      expect(receivedZones).toEqual([Zone.root.name]);
 | 
						|
 | 
						|
      receivedEvents = [];
 | 
						|
      remover1 && remover1();
 | 
						|
      remover2 && remover2();
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([]);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should be able to remove event listener which was added inside of ngZone', () => {
 | 
						|
      const Zone = (window as any)['Zone'];
 | 
						|
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvents: any[] /** TODO #9100 */ = [];
 | 
						|
      let receivedZones: any[] = [];
 | 
						|
      const handler1 = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
      };
 | 
						|
      const handler2 = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvents.push(e);
 | 
						|
        receivedZones.push(Zone.current.name);
 | 
						|
      };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
 | 
						|
      let remover1: any = null;
 | 
						|
      let remover2: any = null;
 | 
						|
      // handler1 is added in root zone
 | 
						|
      Zone.root.run(() => { remover1 = manager.addEventListener(element, 'click', handler1); });
 | 
						|
      // handler2 is added in 'angular' zone
 | 
						|
      Zone.root.fork({name: 'fakeAngularZone', properties: {isAngularZone: true}}).run(() => {
 | 
						|
        remover2 = manager.addEventListener(element, 'click', handler2);
 | 
						|
      });
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvents).toEqual([dispatchedEvent, dispatchedEvent]);
 | 
						|
      expect(receivedZones).toEqual([Zone.root.name, 'fakeAngularZone']);
 | 
						|
 | 
						|
      receivedEvents = [];
 | 
						|
      remover1 && remover1();
 | 
						|
      remover2 && remover2();
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      // handler1 and handler2 are added in different zone
 | 
						|
      // one is angular zone, the other is not
 | 
						|
      // should still be able to remove them correctly
 | 
						|
      expect(receivedEvents).toEqual([]);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should run blackListedEvents handler outside of ngZone', () => {
 | 
						|
      const Zone = (window as any)['Zone'];
 | 
						|
      const element = el('<div><div></div></div>');
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('scroll');
 | 
						|
      let receivedEvent: any /** TODO #9100 */ = null;
 | 
						|
      let receivedZone: any = null;
 | 
						|
      const handler = (e: any /** TODO #9100 */) => {
 | 
						|
        receivedEvent = e;
 | 
						|
        receivedZone = Zone.current;
 | 
						|
      };
 | 
						|
      const manager = new EventManager([domEventPlugin], new FakeNgZone());
 | 
						|
 | 
						|
      let remover = manager.addEventListener(element, 'scroll', handler);
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvent).toBe(dispatchedEvent);
 | 
						|
      expect(receivedZone.name).not.toEqual('angular');
 | 
						|
 | 
						|
      receivedEvent = null;
 | 
						|
      remover && remover();
 | 
						|
      getDOM().dispatchEvent(element, dispatchedEvent);
 | 
						|
      expect(receivedEvent).toBe(null);
 | 
						|
    });
 | 
						|
 | 
						|
    it('should only trigger one Change detection when bubbling', (done: DoneFn) => {
 | 
						|
      doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
 | 
						|
      zone = new NgZone({shouldCoalesceEventChangeDetection: true});
 | 
						|
      domEventPlugin = new DomEventsPlugin(doc);
 | 
						|
      const element = el('<div></div>');
 | 
						|
      const child = el('<div></div>');
 | 
						|
      element.appendChild(child);
 | 
						|
      doc.body.appendChild(element);
 | 
						|
      const dispatchedEvent = createMouseEvent('click');
 | 
						|
      let receivedEvents: any = [];
 | 
						|
      let stables: any = [];
 | 
						|
      const handler = (e: any) => { receivedEvents.push(e); };
 | 
						|
      const manager = new EventManager([domEventPlugin], zone);
 | 
						|
      let removerChild: any;
 | 
						|
      let removerParent: any;
 | 
						|
 | 
						|
      zone.run(() => {
 | 
						|
        removerChild = manager.addEventListener(child, 'click', handler);
 | 
						|
        removerParent = manager.addEventListener(element, 'click', handler);
 | 
						|
      });
 | 
						|
      zone.onStable.subscribe((isStable: any) => { stables.push(isStable); });
 | 
						|
      getDOM().dispatchEvent(child, dispatchedEvent);
 | 
						|
      requestAnimationFrame(() => {
 | 
						|
        expect(receivedEvents.length).toBe(2);
 | 
						|
        expect(stables.length).toBe(1);
 | 
						|
 | 
						|
        removerChild && removerChild();
 | 
						|
        removerParent && removerParent();
 | 
						|
        done();
 | 
						|
      });
 | 
						|
    });
 | 
						|
  });
 | 
						|
})();
 | 
						|
 | 
						|
/** @internal */
 | 
						|
class FakeEventManagerPlugin extends EventManagerPlugin {
 | 
						|
  eventHandler: {[event: string]: Function} = {};
 | 
						|
 | 
						|
  constructor(doc: any, public supportedEvents: string[]) { super(doc); }
 | 
						|
 | 
						|
  supports(eventName: string): boolean { return this.supportedEvents.indexOf(eventName) > -1; }
 | 
						|
 | 
						|
  addEventListener(element: any, eventName: string, handler: Function) {
 | 
						|
    this.eventHandler[eventName] = handler;
 | 
						|
    return () => { delete this.eventHandler[eventName]; };
 | 
						|
  }
 | 
						|
}
 | 
						|
 | 
						|
class FakeNgZone extends NgZone {
 | 
						|
  constructor() { super({enableLongStackTrace: false, shouldCoalesceEventChangeDetection: true}); }
 | 
						|
  run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T { return fn(); }
 | 
						|
  runOutsideAngular(fn: Function) { return fn(); }
 | 
						|
}
 |