2016-06-23 12:47:54 -04:00
|
|
|
/**
|
|
|
|
* @license
|
2020-05-19 15:08:49 -04:00
|
|
|
* Copyright Google LLC All Rights Reserved.
|
2016-06-23 12:47:54 -04: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
|
|
|
|
*/
|
|
|
|
|
2019-08-22 22:16:25 -04:00
|
|
|
import {ɵgetDOM as getDOM} from '@angular/common';
|
2016-04-28 20:50:03 -04:00
|
|
|
import {NgZone} from '@angular/core/src/zone/ng_zone';
|
2017-03-02 15:12:46 -05:00
|
|
|
import {beforeEach, describe, expect, it} from '@angular/core/testing/src/testing_internal';
|
2016-08-02 18:53:34 -04:00
|
|
|
import {DomEventsPlugin} from '@angular/platform-browser/src/dom/events/dom_events';
|
2016-06-08 19:38:52 -04:00
|
|
|
import {EventManager, EventManagerPlugin} from '@angular/platform-browser/src/dom/events/event_manager';
|
2019-08-27 19:21:39 -04:00
|
|
|
import {createMouseEvent, el} from '../../../testing/src/browser_util';
|
2015-02-09 09:11:31 -05:00
|
|
|
|
2017-12-18 01:18:50 -05:00
|
|
|
(function() {
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
|
|
|
});
|
2016-02-10 19:54:32 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
|
|
|
});
|
2015-02-09 09:11:31 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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');
|
|
|
|
});
|
2015-02-19 22:10:16 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
|
|
|
});
|
2015-02-19 22:10:16 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
|
|
|
});
|
2015-02-19 22:10:16 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
2015-02-09 09:11:31 -05:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
|
|
|
});
|
2015-04-02 09:56:58 -04:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
2015-04-02 09:56:58 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
Zone.root.fork({name: 'test'}).run(() => {
|
|
|
|
remover2 = manager.addEventListener(element, 'click', handler2);
|
2017-09-01 13:30:37 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
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([]);
|
|
|
|
});
|
2017-09-01 13:30:37 -04:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
2017-09-01 13:30:37 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
Zone.root.fork({name: 'test'}).run(() => {
|
|
|
|
remover2 = manager.addEventListener(element, 'click', handler2);
|
2017-11-15 21:43:53 -05:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
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([]);
|
|
|
|
});
|
2017-11-15 21:43:53 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
2017-09-01 13:30:37 -04:00
|
|
|
remover1 && remover1();
|
2020-04-13 19:40:21 -04:00
|
|
|
};
|
|
|
|
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);
|
2017-09-01 13:30:37 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
Zone.root.fork({name: 'test'}).run(() => {
|
|
|
|
remover2 = manager.addEventListener(element, 'click', handler2);
|
2017-09-01 13:30:37 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
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([]);
|
|
|
|
});
|
2017-09-01 13:30:37 -04:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
2017-11-20 02:33:37 -05:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
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([]);
|
|
|
|
});
|
2017-11-20 02:33:37 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
2017-08-31 02:52:51 -04:00
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
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([]);
|
|
|
|
});
|
|
|
|
|
2020-09-21 17:15:52 -04:00
|
|
|
it('should run unpatchedEvents handler outside of ngZone', () => {
|
2020-04-13 19:40:21 -04:00
|
|
|
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);
|
|
|
|
});
|
2019-05-17 07:50:02 -04:00
|
|
|
|
2020-10-23 07:45:51 -04:00
|
|
|
it('should only trigger one Change detection when bubbling with shouldCoalesceEventChangeDetection = true',
|
|
|
|
(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();
|
|
|
|
});
|
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
|
2020-10-23 07:45:51 -04:00
|
|
|
it('should only trigger one Change detection when bubbling with shouldCoalesceRunChangeDetection = true',
|
|
|
|
(done: DoneFn) => {
|
|
|
|
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
|
|
|
zone = new NgZone({shouldCoalesceRunChangeDetection: 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();
|
|
|
|
});
|
|
|
|
});
|
fix(core): should fake a top event task when coalescing events to prevent draining microTaskQueue too early. (#36841)
Close #36839.
This is a known issue of zone.js,
```
(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();
});
```
Since in this case, we use native `setTimeout` which is not a ZoneTask,
so zone.js consider the button click handler as the top Task then drain the
microTaskQueue after the click at once, which is not correct(too early).
This case was an edge case and not reported by the users, until we have the
new option ngZoneEventCoalescing, since the event coalescing will happen
in native requestAnimationFrame, so it will not be a ZoneTask, and zone.js will
consider any Task happen in the change detection stage as the top task, and if
there are any microTasks(such as Promise.then) happen in the process, it may be
drained earlier than it should be, so to prevent this situation, we need to schedule
a fake event task and run the change detection check in this fake event task,
so the Task happen in the change detection stage will not be
considered as top ZoneTask.
PR Close #36841
2020-04-29 21:51:01 -04:00
|
|
|
|
|
|
|
it('should not drain micro tasks queue too early with shouldCoalesceEventChangeDetection=true',
|
|
|
|
(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>');
|
|
|
|
doc.body.appendChild(element);
|
|
|
|
const dispatchedClickEvent = createMouseEvent('click');
|
|
|
|
const dispatchedBlurEvent: FocusEvent =
|
|
|
|
getDOM().getDefaultDocument().createEvent('FocusEvent');
|
2020-10-23 07:45:51 -04:00
|
|
|
dispatchedBlurEvent.initEvent('blur', true, true);
|
|
|
|
let logs: any = [];
|
|
|
|
const handler = () => {};
|
|
|
|
|
|
|
|
const blurHandler = (e: any) => {
|
|
|
|
logs.push('blur');
|
|
|
|
};
|
|
|
|
const manager = new EventManager([domEventPlugin], zone);
|
|
|
|
let removerParent: any;
|
|
|
|
let removerChildFocus: any;
|
|
|
|
|
|
|
|
zone.run(() => {
|
|
|
|
removerParent = manager.addEventListener(element, 'click', handler);
|
|
|
|
removerChildFocus = manager.addEventListener(child, 'blur', blurHandler);
|
|
|
|
});
|
|
|
|
const sub = zone.onStable.subscribe(() => {
|
|
|
|
logs.push('begin');
|
|
|
|
Promise.resolve().then(() => {
|
|
|
|
logs.push('promise resolved');
|
|
|
|
});
|
|
|
|
element.appendChild(child);
|
|
|
|
getDOM().dispatchEvent(child, dispatchedBlurEvent);
|
|
|
|
sub.unsubscribe();
|
|
|
|
logs.push('end');
|
|
|
|
});
|
|
|
|
getDOM().dispatchEvent(element, dispatchedClickEvent);
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
expect(logs).toEqual(['begin', 'blur', 'end', 'promise resolved']);
|
|
|
|
|
|
|
|
removerParent && removerParent();
|
|
|
|
removerChildFocus && removerChildFocus();
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
|
|
|
|
|
|
|
it('should not drain micro tasks queue too early with shouldCoalesceRunChangeDetection=true',
|
|
|
|
(done: DoneFn) => {
|
|
|
|
doc = getDOM().supportsDOMEvents() ? document : getDOM().createHtmlDocument();
|
|
|
|
zone = new NgZone({shouldCoalesceRunChangeDetection: true});
|
|
|
|
domEventPlugin = new DomEventsPlugin(doc);
|
|
|
|
const element = el('<div></div>');
|
|
|
|
const child = el('<div></div>');
|
|
|
|
doc.body.appendChild(element);
|
|
|
|
const dispatchedClickEvent = createMouseEvent('click');
|
|
|
|
const dispatchedBlurEvent: FocusEvent =
|
|
|
|
getDOM().getDefaultDocument().createEvent('FocusEvent');
|
fix(core): should fake a top event task when coalescing events to prevent draining microTaskQueue too early. (#36841)
Close #36839.
This is a known issue of zone.js,
```
(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();
});
```
Since in this case, we use native `setTimeout` which is not a ZoneTask,
so zone.js consider the button click handler as the top Task then drain the
microTaskQueue after the click at once, which is not correct(too early).
This case was an edge case and not reported by the users, until we have the
new option ngZoneEventCoalescing, since the event coalescing will happen
in native requestAnimationFrame, so it will not be a ZoneTask, and zone.js will
consider any Task happen in the change detection stage as the top task, and if
there are any microTasks(such as Promise.then) happen in the process, it may be
drained earlier than it should be, so to prevent this situation, we need to schedule
a fake event task and run the change detection check in this fake event task,
so the Task happen in the change detection stage will not be
considered as top ZoneTask.
PR Close #36841
2020-04-29 21:51:01 -04:00
|
|
|
dispatchedBlurEvent.initEvent('blur', true, true);
|
|
|
|
let logs: any = [];
|
|
|
|
const handler = () => {};
|
|
|
|
|
|
|
|
const blurHandler = (e: any) => {
|
|
|
|
logs.push('blur');
|
|
|
|
};
|
|
|
|
const manager = new EventManager([domEventPlugin], zone);
|
|
|
|
let removerParent: any;
|
|
|
|
let removerChildFocus: any;
|
|
|
|
|
|
|
|
zone.run(() => {
|
|
|
|
removerParent = manager.addEventListener(element, 'click', handler);
|
|
|
|
removerChildFocus = manager.addEventListener(child, 'blur', blurHandler);
|
|
|
|
});
|
|
|
|
const sub = zone.onStable.subscribe(() => {
|
|
|
|
logs.push('begin');
|
|
|
|
Promise.resolve().then(() => {
|
|
|
|
logs.push('promise resolved');
|
|
|
|
});
|
|
|
|
element.appendChild(child);
|
|
|
|
getDOM().dispatchEvent(child, dispatchedBlurEvent);
|
|
|
|
sub.unsubscribe();
|
|
|
|
logs.push('end');
|
|
|
|
});
|
|
|
|
getDOM().dispatchEvent(element, dispatchedClickEvent);
|
|
|
|
requestAnimationFrame(() => {
|
|
|
|
expect(logs).toEqual(['begin', 'blur', 'end', 'promise resolved']);
|
|
|
|
|
|
|
|
removerParent && removerParent();
|
|
|
|
removerChildFocus && removerChildFocus();
|
|
|
|
done();
|
|
|
|
});
|
|
|
|
});
|
2020-04-13 19:40:21 -04:00
|
|
|
});
|
2017-12-18 01:18:50 -05:00
|
|
|
})();
|
2015-02-09 09:11:31 -05:00
|
|
|
|
2016-09-19 20:15:57 -04:00
|
|
|
/** @internal */
|
2015-02-09 09:11:31 -05:00
|
|
|
class FakeEventManagerPlugin extends EventManagerPlugin {
|
2016-09-19 20:15:57 -04:00
|
|
|
eventHandler: {[event: string]: Function} = {};
|
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
constructor(doc: any, public supportedEvents: string[]) {
|
|
|
|
super(doc);
|
|
|
|
}
|
2015-02-09 09:11:31 -05:00
|
|
|
|
2020-04-13 19:40:21 -04:00
|
|
|
supports(eventName: string): boolean {
|
|
|
|
return this.supportedEvents.indexOf(eventName) > -1;
|
|
|
|
}
|
2015-02-09 09:11:31 -05:00
|
|
|
|
2016-09-19 20:15:57 -04:00
|
|
|
addEventListener(element: any, eventName: string, handler: Function) {
|
|
|
|
this.eventHandler[eventName] = handler;
|
2020-04-13 19:40:21 -04:00
|
|
|
return () => {
|
|
|
|
delete this.eventHandler[eventName];
|
|
|
|
};
|
2015-02-09 09:11:31 -05:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2015-10-08 16:33:22 -04:00
|
|
|
class FakeNgZone extends NgZone {
|
2020-04-13 19:40:21 -04:00
|
|
|
constructor() {
|
|
|
|
super({enableLongStackTrace: false, shouldCoalesceEventChangeDetection: true});
|
|
|
|
}
|
|
|
|
run<T>(fn: (...args: any[]) => T, applyThis?: any, applyArgs?: any[]): T {
|
|
|
|
return fn();
|
|
|
|
}
|
|
|
|
runOutsideAngular(fn: Function) {
|
|
|
|
return fn();
|
|
|
|
}
|
2015-02-09 09:11:31 -05:00
|
|
|
}
|