mirror of
https://github.com/angular/angular
synced 2026-05-24 09:28:37 +00:00
This commit removes the support for legacy browsers. BREAKING CHANGE: IE/Non-Chromium Edge are not supported anymore. PR Close #63511
3930 lines
133 KiB
TypeScript
3930 lines
133 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.dev/license
|
|
*/
|
|
|
|
import {patchFilteredProperties} from '../../lib/browser/property-descriptor';
|
|
import {patchEventTarget} from '../../lib/common/events';
|
|
import {zoneSymbol} from '../../lib/common/utils';
|
|
import {ifEnvSupports, ifEnvSupportsWithDone} from '../test-util';
|
|
|
|
import Spy = jasmine.Spy;
|
|
declare const global: any;
|
|
|
|
const noop = function () {};
|
|
|
|
function windowPrototype() {
|
|
return !!(global['Window'] && global['Window'].prototype);
|
|
}
|
|
|
|
function promiseUnhandleRejectionSupport() {
|
|
return !!global['PromiseRejectionEvent'];
|
|
}
|
|
|
|
function canPatchOnProperty(obj: any, prop: string) {
|
|
const func = function () {
|
|
if (!obj) {
|
|
return false;
|
|
}
|
|
const desc = Object.getOwnPropertyDescriptor(obj, prop);
|
|
if (!desc || !desc.configurable) {
|
|
return false;
|
|
}
|
|
return true;
|
|
};
|
|
|
|
(func as any).message = 'patchOnProperties';
|
|
return func;
|
|
}
|
|
|
|
let supportsPassive = false;
|
|
try {
|
|
const opts = Object.defineProperty({}, 'passive', {
|
|
get: function () {
|
|
supportsPassive = true;
|
|
},
|
|
});
|
|
window.addEventListener('test', opts as any, opts);
|
|
window.removeEventListener('test', opts as any, opts);
|
|
} catch (e) {}
|
|
|
|
function supportEventListenerOptions() {
|
|
return supportsPassive;
|
|
}
|
|
|
|
(supportEventListenerOptions as any).message = 'supportsEventListenerOptions';
|
|
|
|
function supportCanvasTest() {
|
|
const HTMLCanvasElement = (window as any)['HTMLCanvasElement'];
|
|
const supportCanvas =
|
|
typeof HTMLCanvasElement !== 'undefined' &&
|
|
HTMLCanvasElement.prototype &&
|
|
HTMLCanvasElement.prototype.toBlob;
|
|
const FileReader = (window as any)['FileReader'];
|
|
const supportFileReader = typeof FileReader !== 'undefined';
|
|
return supportCanvas && supportFileReader;
|
|
}
|
|
|
|
(supportCanvasTest as any).message = 'supportCanvasTest';
|
|
|
|
class TestEventListener {
|
|
logs: any[] = [];
|
|
addEventListener(eventName: string, listener: any, options: any) {
|
|
this.logs.push(options);
|
|
}
|
|
removeEventListener(eventName: string, listener: any, options: any) {}
|
|
}
|
|
|
|
describe('Zone', function () {
|
|
const rootZone = Zone.current;
|
|
(Zone as any)[zoneSymbol('ignoreConsoleErrorUncaughtError')] = true;
|
|
|
|
describe('hooks', function () {
|
|
it('should allow you to override alert/prompt/confirm', function () {
|
|
const alertSpy = jasmine.createSpy('alert');
|
|
const promptSpy = jasmine.createSpy('prompt');
|
|
const confirmSpy = jasmine.createSpy('confirm');
|
|
const spies: {[k: string]: Function} = {
|
|
'alert': alertSpy,
|
|
'prompt': promptSpy,
|
|
'confirm': confirmSpy,
|
|
};
|
|
const myZone = Zone.current.fork({
|
|
name: 'spy',
|
|
onInvoke: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
callback: Function,
|
|
applyThis?: any,
|
|
applyArgs?: any[],
|
|
source?: string,
|
|
): any => {
|
|
if (source) {
|
|
spies[source].apply(null, applyArgs);
|
|
} else {
|
|
return parentZoneDelegate.invoke(targetZone, callback, applyThis, applyArgs, source);
|
|
}
|
|
},
|
|
});
|
|
|
|
myZone.run(function () {
|
|
alert('alertMsg');
|
|
prompt('promptMsg', 'default');
|
|
confirm('confirmMsg');
|
|
});
|
|
|
|
expect(alertSpy).toHaveBeenCalledWith('alertMsg');
|
|
expect(promptSpy).toHaveBeenCalledWith('promptMsg', 'default');
|
|
expect(confirmSpy).toHaveBeenCalledWith('confirmMsg');
|
|
});
|
|
|
|
describe(
|
|
'DOM onProperty hooks',
|
|
ifEnvSupports(canPatchOnProperty(HTMLElement.prototype, 'onclick'), function () {
|
|
let mouseEvent = document.createEvent('Event');
|
|
let hookSpy: Spy, eventListenerSpy: Spy;
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
beforeEach(function () {
|
|
mouseEvent.initEvent('mousedown', true, true);
|
|
hookSpy = jasmine.createSpy('hook');
|
|
eventListenerSpy = jasmine.createSpy('eventListener');
|
|
});
|
|
const globalEventHandlersEventNames = [
|
|
'abort',
|
|
'animationcancel',
|
|
'animationend',
|
|
'animationiteration',
|
|
'auxclick',
|
|
'beforeinput',
|
|
'blur',
|
|
'cancel',
|
|
'canplay',
|
|
'canplaythrough',
|
|
'change',
|
|
'compositionstart',
|
|
'compositionupdate',
|
|
'compositionend',
|
|
'cuechange',
|
|
'click',
|
|
'close',
|
|
'contextmenu',
|
|
'curechange',
|
|
'dblclick',
|
|
'drag',
|
|
'dragend',
|
|
'dragenter',
|
|
'dragexit',
|
|
'dragleave',
|
|
'dragover',
|
|
'drop',
|
|
'durationchange',
|
|
'emptied',
|
|
'ended',
|
|
'error',
|
|
'focus',
|
|
'focusin',
|
|
'focusout',
|
|
'gotpointercapture',
|
|
'input',
|
|
'invalid',
|
|
'keydown',
|
|
'keypress',
|
|
'keyup',
|
|
'load',
|
|
'loadstart',
|
|
'loadeddata',
|
|
'loadedmetadata',
|
|
'lostpointercapture',
|
|
'mousedown',
|
|
'mouseenter',
|
|
'mouseleave',
|
|
'mousemove',
|
|
'mouseout',
|
|
'mouseover',
|
|
'mouseup',
|
|
'mousewheel',
|
|
'orientationchange',
|
|
'pause',
|
|
'play',
|
|
'playing',
|
|
'pointercancel',
|
|
'pointerdown',
|
|
'pointerenter',
|
|
'pointerleave',
|
|
'pointerlockchange',
|
|
'mozpointerlockchange',
|
|
'webkitpointerlockerchange',
|
|
'pointerlockerror',
|
|
'mozpointerlockerror',
|
|
'webkitpointerlockerror',
|
|
'pointermove',
|
|
'pointout',
|
|
'pointerover',
|
|
'pointerup',
|
|
'progress',
|
|
'ratechange',
|
|
'reset',
|
|
'resize',
|
|
'scroll',
|
|
'seeked',
|
|
'seeking',
|
|
'select',
|
|
'selectionchange',
|
|
'selectstart',
|
|
'show',
|
|
'sort',
|
|
'stalled',
|
|
'submit',
|
|
'suspend',
|
|
'timeupdate',
|
|
'volumechange',
|
|
'touchcancel',
|
|
'touchmove',
|
|
'touchstart',
|
|
'touchend',
|
|
'transitioncancel',
|
|
'transitionend',
|
|
'waiting',
|
|
'wheel',
|
|
];
|
|
const documentEventNames = [
|
|
'afterscriptexecute',
|
|
'beforescriptexecute',
|
|
'DOMContentLoaded',
|
|
'freeze',
|
|
'fullscreenchange',
|
|
'mozfullscreenchange',
|
|
'webkitfullscreenchange',
|
|
'msfullscreenchange',
|
|
'fullscreenerror',
|
|
'mozfullscreenerror',
|
|
'webkitfullscreenerror',
|
|
'msfullscreenerror',
|
|
'readystatechange',
|
|
'visibilitychange',
|
|
'resume',
|
|
];
|
|
const windowEventNames = [
|
|
'absolutedeviceorientation',
|
|
'afterinput',
|
|
'afterprint',
|
|
'appinstalled',
|
|
'beforeinstallprompt',
|
|
'beforeprint',
|
|
'beforeunload',
|
|
'devicelight',
|
|
'devicemotion',
|
|
'deviceorientation',
|
|
'deviceorientationabsolute',
|
|
'deviceproximity',
|
|
'hashchange',
|
|
'languagechange',
|
|
'message',
|
|
'mozbeforepaint',
|
|
'offline',
|
|
'online',
|
|
'paint',
|
|
'pageshow',
|
|
'pagehide',
|
|
'popstate',
|
|
'rejectionhandled',
|
|
'storage',
|
|
'unhandledrejection',
|
|
'unload',
|
|
'userproximity',
|
|
'vrdisplayconnected',
|
|
'vrdisplaydisconnected',
|
|
'vrdisplaypresentchange',
|
|
];
|
|
const htmlElementEventNames = [
|
|
'beforecopy',
|
|
'beforecut',
|
|
'beforepaste',
|
|
'copy',
|
|
'cut',
|
|
'paste',
|
|
'dragstart',
|
|
'loadend',
|
|
'animationstart',
|
|
'search',
|
|
'transitionrun',
|
|
'transitionstart',
|
|
'webkitanimationend',
|
|
'webkitanimationiteration',
|
|
'webkitanimationstart',
|
|
'webkittransitionend',
|
|
];
|
|
const mediaElementEventNames = [
|
|
'encrypted',
|
|
'waitingforkey',
|
|
'msneedkey',
|
|
'mozinterruptbegin',
|
|
'mozinterruptend',
|
|
];
|
|
const webglEventNames = [
|
|
'webglcontextrestored',
|
|
'webglcontextlost',
|
|
'webglcontextcreationerror',
|
|
];
|
|
const formEventNames = ['autocomplete', 'autocompleteerror'];
|
|
const detailEventNames = ['toggle'];
|
|
const frameEventNames = ['load'];
|
|
const frameSetEventNames = [
|
|
'blur',
|
|
'error',
|
|
'focus',
|
|
'load',
|
|
'resize',
|
|
'scroll',
|
|
'messageerror',
|
|
];
|
|
const marqueeEventNames = ['bounce', 'finish', 'start'];
|
|
|
|
const XMLHttpRequestEventNames = [
|
|
'loadstart',
|
|
'progress',
|
|
'abort',
|
|
'error',
|
|
'load',
|
|
'progress',
|
|
'timeout',
|
|
'loadend',
|
|
'readystatechange',
|
|
];
|
|
const IDBIndexEventNames = [
|
|
'upgradeneeded',
|
|
'complete',
|
|
'abort',
|
|
'success',
|
|
'error',
|
|
'blocked',
|
|
'versionchange',
|
|
'close',
|
|
];
|
|
const websocketEventNames = ['close', 'error', 'open', 'message'];
|
|
const workerEventNames = ['error', 'message'];
|
|
|
|
const eventNames = globalEventHandlersEventNames.concat(
|
|
webglEventNames,
|
|
formEventNames,
|
|
detailEventNames,
|
|
documentEventNames,
|
|
windowEventNames,
|
|
htmlElementEventNames,
|
|
);
|
|
|
|
function checkIsOnPropertiesPatched(
|
|
target: any,
|
|
shouldPatchedProperties?: string[],
|
|
ignoredProperties?: string[],
|
|
) {
|
|
let checkTargetProps =
|
|
shouldPatchedProperties && shouldPatchedProperties.map((p) => `on${p}`);
|
|
if (!checkTargetProps) {
|
|
checkTargetProps = [];
|
|
for (let prop in target) {
|
|
checkTargetProps.push(prop);
|
|
}
|
|
}
|
|
for (let i = 0; i < checkTargetProps.length; i++) {
|
|
const prop = checkTargetProps[i];
|
|
if (
|
|
ignoredProperties &&
|
|
ignoredProperties.filter((ignoreProp) => ignoreProp === prop).length > 0
|
|
) {
|
|
continue;
|
|
}
|
|
if (prop.slice(0, 2) === 'on' && prop.length > 2) {
|
|
let propExistsOnTarget = false;
|
|
let checkTarget = target;
|
|
while (checkTarget && checkTarget !== Object) {
|
|
const desc = Object.getOwnPropertyDescriptor(checkTarget, prop);
|
|
if (desc && desc.configurable) {
|
|
propExistsOnTarget = true;
|
|
break;
|
|
}
|
|
checkTarget = Object.getPrototypeOf(checkTarget);
|
|
}
|
|
if (!propExistsOnTarget) {
|
|
//console.warn(`${prop} not exists on target ${target}`);
|
|
continue;
|
|
}
|
|
target[prop] = noop;
|
|
if (!target[Zone.__symbol__('ON_PROPERTY' + prop.slice(2))]) {
|
|
fail(`${prop} of ${target} is not patched`);
|
|
} else {
|
|
expect(target[prop]).toBe(noop);
|
|
target[prop] = null;
|
|
expect(target[Zone.__symbol__('ON_PROPERTY' + prop.slice(2))]).toBeNull();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
it('should patch all possible on properties on native prototype', function () {
|
|
function isPropertyPatched(obj: any, prop: string, prototype?: any) {
|
|
let desc = Object.getOwnPropertyDescriptor(obj, prop);
|
|
if (!desc && prototype) {
|
|
// when patch window object, use prototype to check prop exist or not
|
|
const prototypeDesc = Object.getOwnPropertyDescriptor(prototype, prop);
|
|
if (prototypeDesc) {
|
|
desc = {enumerable: true, configurable: true};
|
|
}
|
|
}
|
|
// if the descriptor not exists or is not configurable
|
|
// just return
|
|
if (!desc || !desc.configurable) {
|
|
return true;
|
|
}
|
|
|
|
const onPropPatchedSymbol = zoneSymbol('on' + prop + 'patched');
|
|
if (obj.hasOwnProperty(onPropPatchedSymbol) && obj[onPropPatchedSymbol]) {
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function isPropertiesPatched(obj: any, properties: string[] | null, prototype?: any) {
|
|
if (properties) {
|
|
for (let i = 0; i < properties.length; i++) {
|
|
if (!isPropertyPatched(obj, 'on' + properties[i], prototype)) {
|
|
fail(`${properties[i]} is not patched on ${obj}`);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
isPropertiesPatched(
|
|
window,
|
|
eventNames.concat(['messageerror']),
|
|
Object.getPrototypeOf(window),
|
|
);
|
|
isPropertiesPatched(Document.prototype, eventNames);
|
|
|
|
if (typeof window['SVGElement'] !== 'undefined') {
|
|
isPropertiesPatched(window['SVGElement'].prototype, eventNames);
|
|
}
|
|
isPropertiesPatched(Element.prototype, eventNames);
|
|
isPropertiesPatched(HTMLElement.prototype, eventNames);
|
|
isPropertiesPatched(HTMLMediaElement.prototype, mediaElementEventNames);
|
|
isPropertiesPatched(
|
|
HTMLFrameSetElement.prototype,
|
|
windowEventNames.concat(frameSetEventNames),
|
|
);
|
|
isPropertiesPatched(
|
|
HTMLBodyElement.prototype,
|
|
windowEventNames.concat(frameSetEventNames),
|
|
);
|
|
isPropertiesPatched(HTMLFrameElement.prototype, frameEventNames);
|
|
isPropertiesPatched(HTMLIFrameElement.prototype, frameEventNames);
|
|
|
|
const HTMLMarqueeElement = window['HTMLMarqueeElement'];
|
|
if (HTMLMarqueeElement) {
|
|
isPropertiesPatched(HTMLMarqueeElement.prototype, marqueeEventNames);
|
|
}
|
|
const Worker = window['Worker'];
|
|
if (Worker) {
|
|
isPropertiesPatched(Worker.prototype, workerEventNames);
|
|
}
|
|
const XMLHttpRequest = window['XMLHttpRequest'];
|
|
if (XMLHttpRequest) {
|
|
// XMLHttpRequest is not available in ServiceWorker, so we need to check here
|
|
isPropertiesPatched(XMLHttpRequest.prototype, XMLHttpRequestEventNames);
|
|
}
|
|
const XMLHttpRequestEventTarget = window['XMLHttpRequestEventTarget'];
|
|
if (XMLHttpRequestEventTarget) {
|
|
isPropertiesPatched(
|
|
XMLHttpRequestEventTarget && XMLHttpRequestEventTarget.prototype,
|
|
XMLHttpRequestEventNames,
|
|
);
|
|
}
|
|
if (typeof IDBIndex !== 'undefined') {
|
|
isPropertiesPatched(IDBIndex.prototype, IDBIndexEventNames);
|
|
isPropertiesPatched(IDBRequest.prototype, IDBIndexEventNames);
|
|
isPropertiesPatched(IDBOpenDBRequest.prototype, IDBIndexEventNames);
|
|
isPropertiesPatched(IDBDatabase.prototype, IDBIndexEventNames);
|
|
isPropertiesPatched(IDBTransaction.prototype, IDBIndexEventNames);
|
|
isPropertiesPatched(IDBCursor.prototype, IDBIndexEventNames);
|
|
}
|
|
const WebSocket = window['WebSocket'];
|
|
if (WebSocket) {
|
|
isPropertiesPatched(WebSocket.prototype, websocketEventNames);
|
|
}
|
|
});
|
|
|
|
it('should patch all possible on properties on element', function () {
|
|
const htmlElementTagNames: string[] = [
|
|
'a',
|
|
'area',
|
|
'audio',
|
|
'base',
|
|
'basefont',
|
|
'blockquote',
|
|
'br',
|
|
'button',
|
|
'canvas',
|
|
'caption',
|
|
'col',
|
|
'colgroup',
|
|
'data',
|
|
'datalist',
|
|
'del',
|
|
'dir',
|
|
'div',
|
|
'dl',
|
|
'embed',
|
|
'fieldset',
|
|
'font',
|
|
'form',
|
|
'frame',
|
|
'frameset',
|
|
'h1',
|
|
'h2',
|
|
'h3',
|
|
'h4',
|
|
'h5',
|
|
'h6',
|
|
'head',
|
|
'hr',
|
|
'html',
|
|
'iframe',
|
|
'img',
|
|
'input',
|
|
'ins',
|
|
'isindex',
|
|
'label',
|
|
'legend',
|
|
'li',
|
|
'link',
|
|
'listing',
|
|
'map',
|
|
'marquee',
|
|
'menu',
|
|
'meta',
|
|
'meter',
|
|
'nextid',
|
|
'ol',
|
|
'optgroup',
|
|
'option',
|
|
'output',
|
|
'p',
|
|
'param',
|
|
'picture',
|
|
'pre',
|
|
'progress',
|
|
'q',
|
|
'script',
|
|
'select',
|
|
'source',
|
|
'span',
|
|
'style',
|
|
'table',
|
|
'tbody',
|
|
'td',
|
|
'template',
|
|
'textarea',
|
|
'tfoot',
|
|
'th',
|
|
'thead',
|
|
'time',
|
|
'title',
|
|
'tr',
|
|
'track',
|
|
'ul',
|
|
'video',
|
|
];
|
|
htmlElementTagNames.forEach((tagName) => {
|
|
checkIsOnPropertiesPatched(document.createElement(tagName), eventNames, [
|
|
'onorientationchange',
|
|
]);
|
|
});
|
|
});
|
|
|
|
it('should patch all possible on properties on svg element', function () {
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
checkIsOnPropertiesPatched(svg, eventNames);
|
|
});
|
|
|
|
it('should patch all possible on properties on media element', function () {
|
|
const audio = document.createElement('audio');
|
|
checkIsOnPropertiesPatched(audio, mediaElementEventNames);
|
|
});
|
|
|
|
it('should patch all possible on properties on frameset element', function () {
|
|
const frameset = document.createElement('frameset');
|
|
checkIsOnPropertiesPatched(frameset, windowEventNames.concat(frameSetEventNames));
|
|
});
|
|
|
|
it('should patch all possible on properties on body', function () {
|
|
checkIsOnPropertiesPatched(document.body, windowEventNames.concat(frameSetEventNames));
|
|
});
|
|
|
|
it('should patch all possible on properties on Frame', function () {
|
|
const frame = document.createElement('frame');
|
|
checkIsOnPropertiesPatched(frame, frameEventNames);
|
|
});
|
|
|
|
it('should patch all possible on properties on marquee element', function () {
|
|
const marquee = document.createElement('marquee');
|
|
checkIsOnPropertiesPatched(marquee, marqueeEventNames);
|
|
});
|
|
|
|
it('should patch all possible on properties on Window', function () {
|
|
checkIsOnPropertiesPatched(window, eventNames.concat(['messageerror']));
|
|
});
|
|
|
|
it('should patch all possible on properties on xhr', function () {
|
|
checkIsOnPropertiesPatched(new XMLHttpRequest(), XMLHttpRequestEventNames);
|
|
});
|
|
|
|
it('should patch all possible on properties on worker', function () {
|
|
checkIsOnPropertiesPatched(
|
|
new Worker('/packages/zone.js/test/assets/empty-worker.js'),
|
|
workerEventNames,
|
|
);
|
|
});
|
|
|
|
it('should patch all possible on properties on websocket', function () {
|
|
try {
|
|
checkIsOnPropertiesPatched(new WebSocket('ws://localhost:8001'), websocketEventNames);
|
|
} catch (e) {
|
|
console.log('error when creating websocket', e);
|
|
}
|
|
});
|
|
|
|
it('should not patch ignored on properties', function () {
|
|
const TestTarget: any = (window as any)['TestTarget'];
|
|
patchFilteredProperties(
|
|
TestTarget.prototype,
|
|
['prop1', 'prop2'],
|
|
global['__Zone_ignore_on_properties'],
|
|
);
|
|
const testTarget = new TestTarget();
|
|
Zone.current.fork({name: 'test'}).run(() => {
|
|
testTarget.onprop1 = function () {
|
|
// onprop1 should not be patched
|
|
expect(Zone.current.name).toEqual('test1');
|
|
};
|
|
testTarget.onprop2 = function () {
|
|
// onprop2 should be patched
|
|
expect(Zone.current.name).toEqual('test');
|
|
};
|
|
});
|
|
|
|
Zone.current.fork({name: 'test1'}).run(() => {
|
|
testTarget.dispatchEvent('prop1');
|
|
testTarget.dispatchEvent('prop2');
|
|
});
|
|
});
|
|
|
|
it('should not patch ignored eventListener', function () {
|
|
let scrollEvent = document.createEvent('Event');
|
|
scrollEvent.initEvent('scroll', true, true);
|
|
|
|
const zone = Zone.current.fork({name: 'run'});
|
|
const div = document.createElement('div');
|
|
document.body.appendChild(div);
|
|
|
|
Zone.current.fork({name: 'scroll'}).run(() => {
|
|
const listener = () => {
|
|
expect(Zone.current.name).toEqual(zone.name);
|
|
div.removeEventListener('scroll', listener);
|
|
};
|
|
div.addEventListener('scroll', listener);
|
|
});
|
|
|
|
zone.run(() => {
|
|
div.dispatchEvent(scrollEvent);
|
|
});
|
|
document.body.removeChild(div);
|
|
});
|
|
|
|
it('non function property starts with on but not event listener should still work as expected', () => {
|
|
(window as any).one_two_three = {foo: 'bar'};
|
|
|
|
expect((window as any).one_two_three).toEqual({foo: 'bar'});
|
|
(window as any).one_two_three = {bar: 'foo'};
|
|
expect((window as any).one_two_three).toEqual({bar: 'foo'});
|
|
(window as any).one_two_three = null;
|
|
expect((window as any).one_two_three).toBeNull();
|
|
});
|
|
|
|
it('function property starts with on but not event listener should still work as expected', () => {
|
|
let called = false;
|
|
const func = function () {
|
|
called = true;
|
|
};
|
|
(window as any).one_two_three = func;
|
|
|
|
expect((window as any).one_two_three).toEqual(func);
|
|
expect(called).toBeFalse();
|
|
(window as any).one_two_three();
|
|
expect(called).toBeTrue();
|
|
(window as any).one_two_three = null;
|
|
expect((window as any).one_two_three).toBeNull();
|
|
});
|
|
|
|
it('should be able to clear on handler added before load zone.js', function () {
|
|
const TestTarget: any = (window as any)['TestTarget'];
|
|
patchFilteredProperties(
|
|
TestTarget.prototype,
|
|
['prop3'],
|
|
global['__Zone_ignore_on_properties'],
|
|
);
|
|
const testTarget = new TestTarget();
|
|
Zone.current.fork({name: 'test'}).run(() => {
|
|
expect(testTarget.onprop3).toBeTruthy();
|
|
const newProp3Handler = function () {};
|
|
testTarget.onprop3 = newProp3Handler;
|
|
expect(testTarget.onprop3).toBe(newProp3Handler);
|
|
testTarget.onprop3 = null;
|
|
expect(!testTarget.onprop3).toBeTruthy();
|
|
testTarget.onprop3 = function () {
|
|
// onprop1 should not be patched
|
|
expect(Zone.current.name).toEqual('test');
|
|
};
|
|
});
|
|
|
|
Zone.current.fork({name: 'test1'}).run(() => {
|
|
testTarget.dispatchEvent('prop3');
|
|
});
|
|
});
|
|
|
|
it(
|
|
'window onmousedown should be in zone',
|
|
ifEnvSupports(canPatchOnProperty(window, 'onmousedown'), function () {
|
|
zone.run(function () {
|
|
window.onmousedown = eventListenerSpy;
|
|
});
|
|
|
|
window.dispatchEvent(mouseEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(eventListenerSpy).toHaveBeenCalled();
|
|
window.removeEventListener('mousedown', eventListenerSpy);
|
|
expect((window as any)[zoneSymbol('ON_PROPERTYmousedown')]).toEqual(eventListenerSpy);
|
|
window.onmousedown = null;
|
|
expect(!!(window as any)[zoneSymbol('ON_PROPERTYmousedown')]).toBeFalsy();
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'window onresize should be patched',
|
|
ifEnvSupports(canPatchOnProperty(window, 'onmousedown'), function () {
|
|
window.onresize = eventListenerSpy;
|
|
const innerResizeProp: any = (window as any)[zoneSymbol('ON_PROPERTYresize')];
|
|
expect(innerResizeProp).toBeTruthy();
|
|
innerResizeProp();
|
|
expect(eventListenerSpy).toHaveBeenCalled();
|
|
window.removeEventListener('resize', eventListenerSpy);
|
|
expect((window as any)[zoneSymbol('ON_PROPERTYresize')]).toEqual(eventListenerSpy);
|
|
window.onresize = null;
|
|
expect(!!(window as any)[zoneSymbol('ON_PROPERTYresize')]).toBeFalsy();
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'document onmousedown should be in zone',
|
|
ifEnvSupports(canPatchOnProperty(Document.prototype, 'onmousedown'), function () {
|
|
zone.run(function () {
|
|
document.onmousedown = eventListenerSpy;
|
|
});
|
|
|
|
document.dispatchEvent(mouseEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(eventListenerSpy).toHaveBeenCalled();
|
|
document.removeEventListener('mousedown', eventListenerSpy);
|
|
expect((document as any)[zoneSymbol('ON_PROPERTYmousedown')]).toEqual(eventListenerSpy);
|
|
document.onmousedown = null;
|
|
expect(!!(document as any)[zoneSymbol('ON_PROPERTYmousedown')]).toBeFalsy();
|
|
}),
|
|
);
|
|
|
|
// TODO: JiaLiPassion, need to find out why the test bundle is not `use strict`.
|
|
xit(
|
|
'event handler with null context should use event.target',
|
|
ifEnvSupports(canPatchOnProperty(Document.prototype, 'onmousedown'), function () {
|
|
const logs: string[] = [];
|
|
const EventTarget = (window as any)['EventTarget'];
|
|
let oriAddEventListener =
|
|
EventTarget && EventTarget.prototype
|
|
? (EventTarget.prototype as any)[zoneSymbol('addEventListener')]
|
|
: (HTMLSpanElement.prototype as any)[zoneSymbol('addEventListener')];
|
|
|
|
if (!oriAddEventListener) {
|
|
// no patched addEventListener found
|
|
return;
|
|
}
|
|
let handler1: Function;
|
|
let handler2: Function;
|
|
|
|
const listener = function () {
|
|
logs.push('listener1');
|
|
};
|
|
|
|
const listener1 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
|
|
HTMLSpanElement.prototype.addEventListener = function (
|
|
eventName: string,
|
|
callback: any,
|
|
) {
|
|
if (eventName === 'click') {
|
|
handler1 = callback;
|
|
} else if (eventName === 'mousedown') {
|
|
handler2 = callback;
|
|
}
|
|
return oriAddEventListener.apply(this, arguments);
|
|
};
|
|
|
|
(HTMLSpanElement.prototype as any)[zoneSymbol('addEventListener')] = null;
|
|
|
|
patchEventTarget(window, null as any, [HTMLSpanElement.prototype]);
|
|
|
|
const span = document.createElement('span');
|
|
document.body.appendChild(span);
|
|
|
|
zone.run(function () {
|
|
span.addEventListener('click', listener);
|
|
span.onmousedown = listener1;
|
|
});
|
|
|
|
expect(handler1!).toBe(handler2!);
|
|
|
|
handler1!.apply(null, [{type: 'click', target: span}]);
|
|
|
|
handler2!.apply(null, [{type: 'mousedown', target: span}]);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
document.body.removeChild(span);
|
|
if (EventTarget) {
|
|
(EventTarget.prototype as any)[zoneSymbol('addEventListener')] = oriAddEventListener;
|
|
} else {
|
|
(HTMLSpanElement.prototype as any)[zoneSymbol('addEventListener')] =
|
|
oriAddEventListener;
|
|
}
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'SVGElement onmousedown should be in zone',
|
|
ifEnvSupports(
|
|
canPatchOnProperty(SVGElement && SVGElement.prototype, 'onmousedown'),
|
|
function () {
|
|
const svg = document.createElementNS('http://www.w3.org/2000/svg', 'svg');
|
|
document.body.appendChild(svg);
|
|
zone.run(function () {
|
|
svg.onmousedown = eventListenerSpy;
|
|
});
|
|
|
|
svg.dispatchEvent(mouseEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(eventListenerSpy).toHaveBeenCalled();
|
|
svg.removeEventListener('mouse', eventListenerSpy);
|
|
document.body.removeChild(svg);
|
|
},
|
|
),
|
|
);
|
|
|
|
it(
|
|
'get window onerror should not throw error',
|
|
ifEnvSupports(canPatchOnProperty(window, 'onerror'), function () {
|
|
const oriOnError = window.onerror;
|
|
const testFn = function () {
|
|
try {
|
|
let onerror = window.onerror;
|
|
window.onerror = function () {};
|
|
onerror = window.onerror;
|
|
} finally {
|
|
window.onerror = oriOnError;
|
|
}
|
|
};
|
|
expect(testFn).not.toThrow();
|
|
}),
|
|
);
|
|
|
|
// TODO: Sort out why the thrown error bubbles up before the window.onerror callback is
|
|
// executed, though confirmed it does get executed and passes the expectations.
|
|
xit(
|
|
'window.onerror callback signature should be (message, source, lineno, colno, error)',
|
|
ifEnvSupportsWithDone(canPatchOnProperty(window, 'onerror'), function (done: DoneFn) {
|
|
const oriOnError = window.onerror;
|
|
let testError = new Error('testError');
|
|
window.onerror = function (
|
|
message: any,
|
|
source?: string,
|
|
lineno?: number,
|
|
colno?: number,
|
|
error?: any,
|
|
) {
|
|
expect(message).toContain('testError');
|
|
expect(error).toBe(testError);
|
|
(window as any).onerror = oriOnError;
|
|
setTimeout(done);
|
|
return true;
|
|
};
|
|
setTimeout(() => {
|
|
throw testError;
|
|
}, 100);
|
|
}),
|
|
);
|
|
}),
|
|
);
|
|
|
|
describe('eventListener hooks', function () {
|
|
let button: HTMLButtonElement;
|
|
let clickEvent: Event;
|
|
|
|
beforeEach(function () {
|
|
button = document.createElement('button');
|
|
clickEvent = document.createEvent('Event');
|
|
clickEvent.initEvent('click', true, true);
|
|
document.body.appendChild(button);
|
|
});
|
|
|
|
afterEach(function () {
|
|
document.body.removeChild(button);
|
|
});
|
|
|
|
it('should support addEventListener', function () {
|
|
const hookSpy = jasmine.createSpy('hook');
|
|
const eventListenerSpy = jasmine.createSpy('eventListener');
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(function () {
|
|
button.addEventListener('click', eventListenerSpy);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(eventListenerSpy).toHaveBeenCalled();
|
|
});
|
|
|
|
it('should be able to access addEventListener information in onScheduleTask', function () {
|
|
const hookSpy = jasmine.createSpy('hook');
|
|
const eventListenerSpy = jasmine.createSpy('eventListener');
|
|
let scheduleButton;
|
|
let scheduleEventName: string | undefined;
|
|
let scheduleCapture: boolean | undefined;
|
|
let scheduleTask;
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
scheduleButton = (task.data as any).taskData.target;
|
|
scheduleEventName = (task.data as any).taskData.eventName;
|
|
scheduleCapture = (task.data as any).taskData.capture;
|
|
scheduleTask = task;
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(function () {
|
|
button.addEventListener('click', eventListenerSpy);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(eventListenerSpy).toHaveBeenCalled();
|
|
expect(scheduleButton).toBe(button as any);
|
|
expect(scheduleEventName).toBe('click');
|
|
expect(scheduleCapture).toBe(false);
|
|
expect(scheduleTask && (scheduleTask as any).data.taskData).toBe(null as any);
|
|
});
|
|
|
|
it(
|
|
'should support addEventListener on window',
|
|
ifEnvSupports(windowPrototype, function () {
|
|
const hookSpy = jasmine.createSpy('hook');
|
|
const eventListenerSpy = jasmine.createSpy('eventListener');
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(function () {
|
|
window.addEventListener('click', eventListenerSpy);
|
|
});
|
|
|
|
window.dispatchEvent(clickEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(eventListenerSpy).toHaveBeenCalled();
|
|
}),
|
|
);
|
|
|
|
it('should support removeEventListener', function () {
|
|
const hookSpy = jasmine.createSpy('hook');
|
|
const eventListenerSpy = jasmine.createSpy('eventListener');
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onCancelTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.cancelTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(function () {
|
|
button.addEventListener('click', eventListenerSpy);
|
|
button.removeEventListener('click', eventListenerSpy);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(eventListenerSpy).not.toHaveBeenCalled();
|
|
});
|
|
|
|
describe(
|
|
'should support addEventListener/removeEventListener with AddEventListenerOptions with capture setting',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let hookSpy: Spy;
|
|
let cancelSpy: Spy;
|
|
let logs: string[];
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
onCancelTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
cancelSpy();
|
|
return parentZoneDelegate.cancelTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
const docListener = () => {
|
|
logs.push('document');
|
|
};
|
|
const btnListener = () => {
|
|
logs.push('button');
|
|
};
|
|
|
|
beforeEach(() => {
|
|
logs = [];
|
|
hookSpy = jasmine.createSpy('hook');
|
|
cancelSpy = jasmine.createSpy('cancel');
|
|
});
|
|
|
|
it('should handle child event when addEventListener with capture true', () => {
|
|
// test capture true
|
|
zone.run(function () {
|
|
(document as any).addEventListener('click', docListener, {capture: true});
|
|
button.addEventListener('click', btnListener);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
|
|
expect(logs).toEqual(['document', 'button']);
|
|
logs = [];
|
|
|
|
(document as any).removeEventListener('click', docListener, {capture: true});
|
|
button.removeEventListener('click', btnListener);
|
|
expect(cancelSpy).toHaveBeenCalled();
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual([]);
|
|
});
|
|
|
|
it('should handle child event when addEventListener with capture true', () => {
|
|
// test capture false
|
|
zone.run(function () {
|
|
(document as any).addEventListener('click', docListener, {capture: false});
|
|
button.addEventListener('click', btnListener);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs).toEqual(['button', 'document']);
|
|
logs = [];
|
|
|
|
(document as any).removeEventListener('click', docListener, {capture: false});
|
|
button.removeEventListener('click', btnListener);
|
|
expect(cancelSpy).toHaveBeenCalled();
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual([]);
|
|
});
|
|
}),
|
|
);
|
|
|
|
describe(
|
|
'should ignore duplicate event handler',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let hookSpy: Spy;
|
|
let cancelSpy: Spy;
|
|
let logs: string[];
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
onCancelTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
cancelSpy();
|
|
return parentZoneDelegate.cancelTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
const docListener = () => {
|
|
logs.push('document options');
|
|
};
|
|
|
|
beforeEach(() => {
|
|
logs = [];
|
|
hookSpy = jasmine.createSpy('hook');
|
|
cancelSpy = jasmine.createSpy('cancel');
|
|
});
|
|
|
|
const testDuplicate = function (args1?: any, args2?: any) {
|
|
zone.run(function () {
|
|
if (args1) {
|
|
(document as any).addEventListener('click', docListener, args1);
|
|
} else {
|
|
(document as any).addEventListener('click', docListener);
|
|
}
|
|
if (args2) {
|
|
(document as any).addEventListener('click', docListener, args2);
|
|
} else {
|
|
(document as any).addEventListener('click', docListener);
|
|
}
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs).toEqual(['document options']);
|
|
logs = [];
|
|
|
|
(document as any).removeEventListener('click', docListener, args1);
|
|
expect(cancelSpy).toHaveBeenCalled();
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual([]);
|
|
};
|
|
|
|
it('should ignore duplicate handler', () => {
|
|
let captureFalse = [
|
|
undefined,
|
|
false,
|
|
{capture: false},
|
|
{capture: false, passive: false},
|
|
{passive: false},
|
|
{},
|
|
];
|
|
let captureTrue = [true, {capture: true}, {capture: true, passive: false}];
|
|
for (let i = 0; i < captureFalse.length; i++) {
|
|
for (let j = 0; j < captureFalse.length; j++) {
|
|
testDuplicate(captureFalse[i], captureFalse[j]);
|
|
}
|
|
}
|
|
for (let i = 0; i < captureTrue.length; i++) {
|
|
for (let j = 0; j < captureTrue.length; j++) {
|
|
testDuplicate(captureTrue[i], captureTrue[j]);
|
|
}
|
|
}
|
|
});
|
|
}),
|
|
);
|
|
|
|
describe(
|
|
'should support mix useCapture with AddEventListenerOptions capture',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let hookSpy: Spy;
|
|
let cancelSpy: Spy;
|
|
let logs: string[];
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
onCancelTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
cancelSpy();
|
|
return parentZoneDelegate.cancelTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
const docListener = () => {
|
|
logs.push('document options');
|
|
};
|
|
const docListener1 = () => {
|
|
logs.push('document useCapture');
|
|
};
|
|
const btnListener = () => {
|
|
logs.push('button');
|
|
};
|
|
|
|
beforeEach(() => {
|
|
logs = [];
|
|
hookSpy = jasmine.createSpy('hook');
|
|
cancelSpy = jasmine.createSpy('cancel');
|
|
});
|
|
|
|
const testAddRemove = function (args1?: any, args2?: any) {
|
|
zone.run(function () {
|
|
if (args1) {
|
|
(document as any).addEventListener('click', docListener, args1);
|
|
} else {
|
|
(document as any).addEventListener('click', docListener);
|
|
}
|
|
if (args2) {
|
|
(document as any).removeEventListener('click', docListener, args2);
|
|
} else {
|
|
(document as any).removeEventListener('click', docListener);
|
|
}
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(cancelSpy).toHaveBeenCalled();
|
|
expect(logs).toEqual([]);
|
|
};
|
|
|
|
it('should be able to add/remove same handler with mix options and capture', function () {
|
|
let captureFalse = [
|
|
undefined,
|
|
false,
|
|
{capture: false},
|
|
{capture: false, passive: false},
|
|
{passive: false},
|
|
{},
|
|
];
|
|
let captureTrue = [true, {capture: true}, {capture: true, passive: false}];
|
|
for (let i = 0; i < captureFalse.length; i++) {
|
|
for (let j = 0; j < captureFalse.length; j++) {
|
|
testAddRemove(captureFalse[i], captureFalse[j]);
|
|
}
|
|
}
|
|
for (let i = 0; i < captureTrue.length; i++) {
|
|
for (let j = 0; j < captureTrue.length; j++) {
|
|
testAddRemove(captureTrue[i], captureTrue[j]);
|
|
}
|
|
}
|
|
});
|
|
|
|
const testDifferent = function (args1?: any, args2?: any) {
|
|
zone.run(function () {
|
|
if (args1) {
|
|
(document as any).addEventListener('click', docListener, args1);
|
|
} else {
|
|
(document as any).addEventListener('click', docListener);
|
|
}
|
|
if (args2) {
|
|
(document as any).addEventListener('click', docListener1, args2);
|
|
} else {
|
|
(document as any).addEventListener('click', docListener1);
|
|
}
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs.sort()).toEqual(['document options', 'document useCapture']);
|
|
logs = [];
|
|
|
|
if (args1) {
|
|
(document as any).removeEventListener('click', docListener, args1);
|
|
} else {
|
|
(document as any).removeEventListener('click', docListener);
|
|
}
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual(['document useCapture']);
|
|
logs = [];
|
|
|
|
if (args2) {
|
|
(document as any).removeEventListener('click', docListener1, args2);
|
|
} else {
|
|
(document as any).removeEventListener('click', docListener1);
|
|
}
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual([]);
|
|
};
|
|
|
|
it('should be able to add different handlers for same event', function () {
|
|
let captureFalse = [
|
|
undefined,
|
|
false,
|
|
{capture: false},
|
|
{capture: false, passive: false},
|
|
{passive: false},
|
|
{},
|
|
];
|
|
let captureTrue = [true, {capture: true}, {capture: true, passive: false}];
|
|
for (let i = 0; i < captureFalse.length; i++) {
|
|
for (let j = 0; j < captureTrue.length; j++) {
|
|
testDifferent(captureFalse[i], captureTrue[j]);
|
|
}
|
|
}
|
|
for (let i = 0; i < captureTrue.length; i++) {
|
|
for (let j = 0; j < captureFalse.length; j++) {
|
|
testDifferent(captureTrue[i], captureFalse[j]);
|
|
}
|
|
}
|
|
});
|
|
|
|
it('should handle options.capture true with capture true correctly', function () {
|
|
zone.run(function () {
|
|
(document as any).addEventListener('click', docListener, {capture: true});
|
|
document.addEventListener('click', docListener1, true);
|
|
button.addEventListener('click', btnListener);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs).toEqual(['document options', 'document useCapture', 'button']);
|
|
logs = [];
|
|
|
|
(document as any).removeEventListener('click', docListener, {capture: true});
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual(['document useCapture', 'button']);
|
|
logs = [];
|
|
|
|
document.removeEventListener('click', docListener1, true);
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual(['button']);
|
|
logs = [];
|
|
|
|
button.removeEventListener('click', btnListener);
|
|
expect(cancelSpy).toHaveBeenCalled();
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual([]);
|
|
(document as any).removeAllListeners('click');
|
|
});
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support addEventListener with AddEventListenerOptions once setting',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let hookSpy = jasmine.createSpy('hook');
|
|
let logs: string[] = [];
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(function () {
|
|
(button as any).addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click');
|
|
},
|
|
{once: true},
|
|
);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click']);
|
|
logs = [];
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support addEventListener with AddEventListenerOptions once setting and capture',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let hookSpy = jasmine.createSpy('hook');
|
|
let logs: string[] = [];
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(function () {
|
|
(button as any).addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click');
|
|
},
|
|
{once: true, capture: true},
|
|
);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click']);
|
|
logs = [];
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support add multiple listeners with AddEventListenerOptions once setting and same capture after normal listener',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let logs: string[] = [];
|
|
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click');
|
|
},
|
|
true,
|
|
);
|
|
(button as any).addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('once click');
|
|
},
|
|
{once: true, capture: true},
|
|
);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click', 'once click']);
|
|
logs = [];
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click']);
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support add multiple listeners with AddEventListenerOptions once setting and mixed capture after normal listener',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let logs: string[] = [];
|
|
|
|
button.addEventListener('click', function () {
|
|
logs.push('click');
|
|
});
|
|
(button as any).addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('once click');
|
|
},
|
|
{once: true, capture: true},
|
|
);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['once click', 'click']);
|
|
logs = [];
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click']);
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support add multiple listeners with AddEventListenerOptions once setting before normal listener',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let logs: string[] = [];
|
|
|
|
(button as any).addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('once click');
|
|
},
|
|
{once: true},
|
|
);
|
|
|
|
button.addEventListener('click', function () {
|
|
logs.push('click');
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['once click', 'click']);
|
|
logs = [];
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click']);
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support add multiple listeners with AddEventListenerOptions once setting with same capture before normal listener',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let logs: string[] = [];
|
|
|
|
(button as any).addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('once click');
|
|
},
|
|
{once: true, capture: true},
|
|
);
|
|
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click');
|
|
},
|
|
true,
|
|
);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['once click', 'click']);
|
|
logs = [];
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click']);
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support add multiple listeners with AddEventListenerOptions once setting with mixed capture before normal listener',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let logs: string[] = [];
|
|
|
|
(button as any).addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('once click');
|
|
},
|
|
{once: true, capture: true},
|
|
);
|
|
|
|
button.addEventListener('click', function () {
|
|
logs.push('click');
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['once click', 'click']);
|
|
logs = [];
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click']);
|
|
}),
|
|
);
|
|
|
|
it('should change options to boolean if not support passive', () => {
|
|
patchEventTarget(window, null as any, [TestEventListener.prototype]);
|
|
const testEventListener = new TestEventListener();
|
|
|
|
const listener = function () {};
|
|
testEventListener.addEventListener('test', listener, {passive: true});
|
|
testEventListener.addEventListener('test1', listener, {once: true});
|
|
testEventListener.addEventListener('test2', listener, {capture: true});
|
|
testEventListener.addEventListener('test3', listener, {passive: false});
|
|
testEventListener.addEventListener('test4', listener, {once: false});
|
|
testEventListener.addEventListener('test5', listener, {capture: false});
|
|
if (!supportsPassive) {
|
|
expect(testEventListener.logs).toEqual([false, false, true, false, false, false]);
|
|
} else {
|
|
expect(testEventListener.logs).toEqual([
|
|
{passive: true},
|
|
{once: true},
|
|
{capture: true},
|
|
{passive: false},
|
|
{once: false},
|
|
{capture: false},
|
|
]);
|
|
}
|
|
});
|
|
|
|
it('should change options to boolean if not support passive on HTMLElement', () => {
|
|
const logs: string[] = [];
|
|
const listener = (e: Event) => {
|
|
logs.push('clicked');
|
|
};
|
|
|
|
(button as any).addEventListener('click', listener, {once: true});
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual(['clicked']);
|
|
button.dispatchEvent(clickEvent);
|
|
if (supportsPassive) {
|
|
expect(logs).toEqual(['clicked']);
|
|
} else {
|
|
expect(logs).toEqual(['clicked', 'clicked']);
|
|
}
|
|
|
|
button.removeEventListener('click', listener);
|
|
});
|
|
|
|
it(
|
|
'should support addEventListener with AddEventListenerOptions passive setting',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
const hookSpy = jasmine.createSpy('hook');
|
|
const logs: string[] = [];
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
const listener = (e: Event) => {
|
|
logs.push(e.defaultPrevented.toString());
|
|
e.preventDefault();
|
|
logs.push(e.defaultPrevented.toString());
|
|
};
|
|
|
|
zone.run(function () {
|
|
(button as any).addEventListener('click', listener, {passive: true});
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs).toEqual(['false', 'false']);
|
|
|
|
button.removeEventListener('click', listener);
|
|
}),
|
|
);
|
|
|
|
describe('passiveEvents by global settings', () => {
|
|
let logs: string[] = [];
|
|
const listener = (e: Event) => {
|
|
logs.push(e.defaultPrevented ? 'defaultPrevented' : 'default will run');
|
|
e.preventDefault();
|
|
logs.push(e.defaultPrevented ? 'defaultPrevented' : 'default will run');
|
|
};
|
|
const testPassive = function (eventName: string, expectedPassiveLog: string, options: any) {
|
|
(button as any).addEventListener(eventName, listener, options);
|
|
const evt = document.createEvent('Event');
|
|
evt.initEvent(eventName, false, true);
|
|
button.dispatchEvent(evt);
|
|
expect(logs).toEqual(['default will run', expectedPassiveLog]);
|
|
(button as any).removeAllListeners(eventName);
|
|
};
|
|
beforeEach(() => {
|
|
logs = [];
|
|
(button as any).removeAllListeners();
|
|
});
|
|
afterEach(() => {
|
|
(button as any).removeAllListeners();
|
|
});
|
|
it('should be passive with global variable defined', () => {
|
|
testPassive('touchstart', 'default will run', {passive: true});
|
|
});
|
|
it('should not be passive without global variable defined', () => {
|
|
testPassive('touchend', 'defaultPrevented', undefined);
|
|
});
|
|
it('should be passive with global variable defined even without passive options', () => {
|
|
testPassive('touchstart', 'default will run', undefined);
|
|
});
|
|
it('should be passive with global variable defined even without passive options and with capture', () => {
|
|
testPassive('touchstart', 'default will run', {capture: true});
|
|
});
|
|
it('should be passive with global variable defined with capture option', () => {
|
|
testPassive('touchstart', 'default will run', true);
|
|
});
|
|
it('should not be passive with global variable defined with passive false option', () => {
|
|
testPassive('touchstart', 'defaultPrevented', {passive: false});
|
|
});
|
|
it('should be passive with global variable defined and also unpatched', () => {
|
|
testPassive('scroll', 'default will run', undefined);
|
|
});
|
|
it('should not be passive without global variable defined and also unpatched', () => {
|
|
testPassive('wheel', 'defaultPrevented', undefined);
|
|
});
|
|
});
|
|
|
|
it(
|
|
'should support Event.stopImmediatePropagation',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
const hookSpy = jasmine.createSpy('hook');
|
|
const logs: any[] = [];
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
const listener1 = (e: Event) => {
|
|
logs.push('listener1');
|
|
e.stopImmediatePropagation();
|
|
};
|
|
|
|
const listener2 = (e: Event) => {
|
|
logs.push('listener2');
|
|
};
|
|
|
|
zone.run(function () {
|
|
(button as any).addEventListener('click', listener1);
|
|
(button as any).addEventListener('click', listener2);
|
|
});
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
expect(hookSpy).toHaveBeenCalled();
|
|
expect(logs).toEqual(['listener1']);
|
|
|
|
button.removeEventListener('click', listener1);
|
|
button.removeEventListener('click', listener2);
|
|
}),
|
|
);
|
|
|
|
it('should support remove event listener by call zone.cancelTask directly', function () {
|
|
let logs: string[] = [];
|
|
let eventTask: Task;
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
eventTask = task;
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(() => {
|
|
button.addEventListener('click', function () {
|
|
logs.push('click');
|
|
});
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(1);
|
|
eventTask!.zone.cancelTask(eventTask!);
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
expect(listeners.length).toBe(0);
|
|
});
|
|
|
|
it('should support remove event listener by call zone.cancelTask directly with capture=true', function () {
|
|
let logs: string[] = [];
|
|
let eventTask: Task;
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
eventTask = task;
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(() => {
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click');
|
|
},
|
|
true,
|
|
);
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(1);
|
|
eventTask!.zone.cancelTask(eventTask!);
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
expect(listeners.length).toBe(0);
|
|
});
|
|
|
|
it('should support remove event listeners by call zone.cancelTask directly with multiple listeners', function () {
|
|
let logs: string[] = [];
|
|
let eventTask: Task;
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
eventTask = task;
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(() => {
|
|
button.addEventListener('click', function () {
|
|
logs.push('click1');
|
|
});
|
|
});
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(2);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click1', 'click2']);
|
|
eventTask!.zone.cancelTask(eventTask!);
|
|
logs = [];
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(listeners.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
});
|
|
|
|
it('should support remove event listeners by call zone.cancelTask directly with multiple listeners with same capture=true', function () {
|
|
let logs: string[] = [];
|
|
let eventTask: Task;
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
eventTask = task;
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(() => {
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click1');
|
|
},
|
|
true,
|
|
);
|
|
});
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click2');
|
|
},
|
|
true,
|
|
);
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(2);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click1', 'click2']);
|
|
eventTask!.zone.cancelTask(eventTask!);
|
|
logs = [];
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(listeners.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
});
|
|
|
|
it('should support remove event listeners by call zone.cancelTask directly with multiple listeners with mixed capture', function () {
|
|
let logs: string[] = [];
|
|
let eventTask: Task;
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
eventTask = task;
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(() => {
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click1');
|
|
},
|
|
true,
|
|
);
|
|
});
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(2);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click1', 'click2']);
|
|
eventTask!.zone.cancelTask(eventTask!);
|
|
logs = [];
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(listeners.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
});
|
|
|
|
it('should support remove event listeners via AbortController', function () {
|
|
let logs: string[] = [];
|
|
const ac = new AbortController();
|
|
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click1');
|
|
},
|
|
{signal: ac.signal},
|
|
);
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click3');
|
|
},
|
|
{signal: ac.signal},
|
|
);
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['click1', 'click2', 'click3']);
|
|
ac.abort();
|
|
logs = [];
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(listeners.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
});
|
|
|
|
it('should support remove event listeners with AbortController', function () {
|
|
let logs: string[] = [];
|
|
const ac = new AbortController();
|
|
|
|
const listener1 = function () {
|
|
logs.push('click1');
|
|
};
|
|
button.addEventListener('click', listener1, {signal: ac.signal});
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(2);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click1', 'click2']);
|
|
|
|
ac.abort();
|
|
listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(1);
|
|
|
|
logs = [];
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(listeners.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
});
|
|
|
|
it('should not break remove event listeners with AbortController', function () {
|
|
let logs: string[] = [];
|
|
const ac = new AbortController();
|
|
|
|
const listener1 = function () {
|
|
logs.push('click1');
|
|
};
|
|
button.addEventListener('click', listener1, {signal: ac.signal});
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(2);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click1', 'click2']);
|
|
|
|
button.removeEventListener('click', listener1);
|
|
listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(1);
|
|
|
|
logs = [];
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(listeners.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
});
|
|
|
|
it('should support remove multiple event listeners with the same AbortController', function () {
|
|
let logs: string[] = [];
|
|
const ac = new AbortController();
|
|
const button1 = document.createElement('button');
|
|
const keyEvent: any = document.createEvent('KeyboardEvent');
|
|
keyEvent.initKeyboardEvent(
|
|
'keypress', // typeArg,
|
|
true, // canBubbleArg,
|
|
true, // cancelableArg,
|
|
null, // viewArg, Specifies UIEvent.view. This value may be null.
|
|
'',
|
|
0,
|
|
false, // ctrlKeyArg,
|
|
false, // altKeyArg,
|
|
false, // shiftKeyArg,
|
|
false, // metaKeyArg,
|
|
);
|
|
document.body.appendChild(button1);
|
|
|
|
const listener1 = function () {
|
|
logs.push('click1');
|
|
};
|
|
button.addEventListener('click', listener1, {signal: ac.signal});
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
button1.addEventListener('keypress', () => logs.push('click3'), {signal: ac.signal});
|
|
button1.addEventListener('keypress', function () {
|
|
logs.push('click4');
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(2);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click1', 'click2']);
|
|
button1.dispatchEvent(keyEvent);
|
|
expect(logs.length).toBe(4);
|
|
expect(logs).toEqual(['click1', 'click2', 'click3', 'click4']);
|
|
|
|
logs = [];
|
|
ac.abort();
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(listeners.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
button1.dispatchEvent(keyEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click2', 'click4']);
|
|
});
|
|
|
|
// https://github.com/angular/angular/issues/54831
|
|
// https://github.com/angular/angular/issues/54142
|
|
it('should support passing `AbortController` directly to `addEventListener`', function () {
|
|
let logs: string[] = [];
|
|
const ac = new AbortController();
|
|
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click1');
|
|
},
|
|
ac,
|
|
);
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click3');
|
|
},
|
|
ac,
|
|
);
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['click1', 'click2', 'click3']);
|
|
ac.abort();
|
|
logs = [];
|
|
|
|
listeners = button.eventListeners!('click');
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(listeners.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
});
|
|
|
|
// https://github.com/angular/angular/issues/56148
|
|
it('should store the remove abort listener on the task itself and not the task data', function () {
|
|
const logs: string[] = [];
|
|
const ac = new AbortController();
|
|
const onLoad = jasmine.createSpy();
|
|
|
|
// Note that we have to manually spy on the `removeEventListener`
|
|
// because zone.js calls the native `addEventListener` for an
|
|
// `AbortSignal`, which isn't patched by zone.js. Thus, calling
|
|
// `signal.eventListeners('abort')` would always return an empty list.
|
|
const removeEventListenerSpy = spyOn(
|
|
AbortSignal.prototype,
|
|
'removeEventListener',
|
|
).and.callThrough();
|
|
|
|
window.addEventListener('load', onLoad, {once: true});
|
|
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click');
|
|
},
|
|
{signal: ac.signal},
|
|
);
|
|
|
|
// In this scenario, we must dispatch the load event because
|
|
// `{once:true}` options are provided. This action will remove
|
|
// the event listener on the `window` after the task is executed,
|
|
// effectively cancelling it. Previously, we encountered a regression
|
|
// where `removeAbortListener` was stored on the task data instead of
|
|
// the task itself. Consequently, it would remove the `abort` listener
|
|
// from the signal. As a result, calling `abort()` on the controller wouldn't
|
|
// cancel any events because there was no `abort` listener present.
|
|
window.dispatchEvent(new Event('load'));
|
|
expect(onLoad).toHaveBeenCalled();
|
|
|
|
// Assert that the `abort` event listener has NOT been removed.
|
|
expect(removeEventListenerSpy).toHaveBeenCalledTimes(0);
|
|
|
|
// When calling abort, it should now remove the event listener
|
|
// from the button, thus dispatching events should not trigger the listener.
|
|
ac.abort();
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
|
|
const listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(0);
|
|
expect(logs).toEqual([]);
|
|
});
|
|
|
|
it('should not add event listeners with aborted signal', function () {
|
|
let logs: string[] = [];
|
|
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click1');
|
|
},
|
|
{signal: AbortSignal.abort()},
|
|
);
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(1);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
});
|
|
|
|
it(
|
|
'should remove event listeners with timeout signal',
|
|
ifEnvSupportsWithDone(
|
|
() => typeof AbortSignal.timeout === 'function',
|
|
function (done: DoneFn) {
|
|
let logs: string[] = [];
|
|
|
|
button.addEventListener(
|
|
'click',
|
|
function () {
|
|
logs.push('click1');
|
|
},
|
|
{signal: AbortSignal.timeout(1)},
|
|
);
|
|
button.addEventListener('click', function () {
|
|
logs.push('click2');
|
|
});
|
|
let listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(2);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['click1', 'click2']);
|
|
|
|
setTimeout(() => {
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['click2']);
|
|
done();
|
|
}, 10);
|
|
},
|
|
),
|
|
);
|
|
|
|
it(
|
|
'should support reschedule eventTask',
|
|
ifEnvSupports(supportEventListenerOptions, function () {
|
|
let hookSpy1 = jasmine.createSpy('spy1');
|
|
let hookSpy2 = jasmine.createSpy('spy2');
|
|
let hookSpy3 = jasmine.createSpy('spy3');
|
|
let logs: string[] = [];
|
|
const isUnpatchedEvent = function (source: string) {
|
|
return source.lastIndexOf('click') !== -1;
|
|
};
|
|
const zone1 = Zone.current.fork({
|
|
name: 'zone1',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
if (
|
|
(task.type === 'eventTask' || task.type === 'macroTask') &&
|
|
isUnpatchedEvent(task.source)
|
|
) {
|
|
task.cancelScheduleRequest();
|
|
|
|
return zone2.scheduleTask(task);
|
|
} else {
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
}
|
|
},
|
|
onInvokeTask(
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
applyThis: any,
|
|
applyArgs: any,
|
|
) {
|
|
hookSpy1();
|
|
return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
|
|
},
|
|
});
|
|
const zone2 = Zone.current.fork({
|
|
name: 'zone2',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy2();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
onInvokeTask(
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
applyThis: any,
|
|
applyArgs: any,
|
|
) {
|
|
hookSpy3();
|
|
return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs);
|
|
},
|
|
});
|
|
|
|
const listener = function () {
|
|
logs.push(Zone.current.name);
|
|
};
|
|
zone1.run(() => {
|
|
button.addEventListener('click', listener);
|
|
button.addEventListener('mouseover', listener);
|
|
});
|
|
|
|
const clickEvent = document.createEvent('Event');
|
|
clickEvent.initEvent('click', true, true);
|
|
const mouseEvent = document.createEvent('Event');
|
|
mouseEvent.initEvent('mouseover', true, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
button.removeEventListener('click', listener);
|
|
|
|
expect(logs).toEqual(['zone2']);
|
|
expect(hookSpy1).not.toHaveBeenCalled();
|
|
expect(hookSpy2).toHaveBeenCalled();
|
|
expect(hookSpy3).toHaveBeenCalled();
|
|
logs = [];
|
|
hookSpy2 = jasmine.createSpy('hookSpy2');
|
|
hookSpy3 = jasmine.createSpy('hookSpy3');
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
button.removeEventListener('mouseover', listener);
|
|
expect(logs).toEqual(['zone1']);
|
|
expect(hookSpy1).toHaveBeenCalled();
|
|
expect(hookSpy2).not.toHaveBeenCalled();
|
|
expect(hookSpy3).not.toHaveBeenCalled();
|
|
}),
|
|
);
|
|
|
|
it('should support inline event handler attributes', function () {
|
|
const hookSpy = jasmine.createSpy('hook');
|
|
const zone = rootZone.fork({
|
|
name: 'spy',
|
|
onScheduleTask: (
|
|
parentZoneDelegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
): any => {
|
|
hookSpy();
|
|
return parentZoneDelegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(function () {
|
|
button.setAttribute('onclick', 'return');
|
|
expect(button.onclick).not.toBe(null);
|
|
});
|
|
});
|
|
|
|
it('should not remove onEventListener when removing capture listener', function () {
|
|
const button = document.createElement('button');
|
|
document.body.append(button);
|
|
const createEvt = () => {
|
|
const evt = document.createEvent('Event');
|
|
evt.initEvent('click', true, true);
|
|
return evt;
|
|
};
|
|
let logs: string[] = [];
|
|
const onClickHandler = () => logs.push('onclick');
|
|
button.onclick = onClickHandler;
|
|
let evt = createEvt();
|
|
button.dispatchEvent(evt);
|
|
expect(logs).toEqual(['onclick']);
|
|
logs = [];
|
|
const listener = () => logs.push('click listener');
|
|
button.addEventListener('click', listener, {capture: true});
|
|
evt = createEvt();
|
|
button.dispatchEvent(evt);
|
|
expect(logs.sort()).toEqual(['onclick', 'click listener'].sort());
|
|
logs = [];
|
|
button.removeEventListener('click', listener, true);
|
|
evt = createEvt();
|
|
button.dispatchEvent(evt);
|
|
expect(logs).toEqual(['onclick']);
|
|
expect(button.onclick).toBe(onClickHandler);
|
|
button.onclick = null;
|
|
logs = [];
|
|
evt = createEvt();
|
|
button.dispatchEvent(evt);
|
|
expect(logs).toEqual([]);
|
|
expect(button.onclick).toBe(null);
|
|
document.body.removeChild(button);
|
|
});
|
|
|
|
describe('should be able to remove eventListener during eventListener callback', function () {
|
|
it('should be able to remove eventListener during eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
button.removeEventListener('click', listener1);
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener2', 'listener3']);
|
|
|
|
button.removeEventListener('click', listener2);
|
|
button.removeEventListener('click', listener3);
|
|
});
|
|
|
|
it('should be able to remove eventListener during eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
button.removeEventListener('click', listener1, true);
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener2', 'listener3']);
|
|
|
|
button.removeEventListener('click', listener2, true);
|
|
button.removeEventListener('click', listener3, true);
|
|
});
|
|
|
|
it('should be able to remove handleEvent eventListener during eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeEventListener('click', listener3);
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
|
|
button.removeEventListener('click', listener1);
|
|
button.removeEventListener('click', listener2);
|
|
});
|
|
|
|
it('should be able to remove handleEvent eventListener during eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeEventListener('click', listener3, true);
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
|
|
button.removeEventListener('click', listener1, true);
|
|
button.removeEventListener('click', listener2, true);
|
|
});
|
|
|
|
it('should be able to remove multiple eventListeners during eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
button.removeEventListener('click', listener2);
|
|
button.removeEventListener('click', listener3);
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['listener1']);
|
|
|
|
button.removeEventListener('click', listener1);
|
|
});
|
|
|
|
it('should be able to remove multiple eventListeners during eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
button.removeEventListener('click', listener2, true);
|
|
button.removeEventListener('click', listener3, true);
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['listener1']);
|
|
|
|
button.removeEventListener('click', listener1, true);
|
|
});
|
|
|
|
it('should be able to remove part of other eventListener during eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
button.removeEventListener('click', listener2);
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener1', 'listener3']);
|
|
|
|
button.removeEventListener('click', listener1);
|
|
button.removeEventListener('click', listener3);
|
|
});
|
|
|
|
it('should be able to remove part of other eventListener during eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
button.removeEventListener('click', listener2, true);
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener1', 'listener3']);
|
|
|
|
button.removeEventListener('click', listener1, true);
|
|
button.removeEventListener('click', listener3, true);
|
|
});
|
|
|
|
it('should be able to remove all beforeward and afterward eventListener during eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
button.removeEventListener('click', listener1);
|
|
button.removeEventListener('click', listener3);
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['listener2']);
|
|
|
|
button.removeEventListener('click', listener2);
|
|
});
|
|
|
|
it('should be able to remove all beforeward and afterward eventListener during eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
button.removeEventListener('click', listener1, true);
|
|
button.removeEventListener('click', listener3, true);
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['listener2']);
|
|
|
|
button.removeEventListener('click', listener2, true);
|
|
});
|
|
|
|
it('should be able to remove part of beforeward and afterward eventListener during eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeEventListener('click', listener2);
|
|
button.removeEventListener('click', listener4);
|
|
},
|
|
};
|
|
const listener4 = function () {
|
|
logs.push('listener4');
|
|
};
|
|
const listener5 = function () {
|
|
logs.push('listener5');
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
button.addEventListener('click', listener4);
|
|
button.addEventListener('click', listener5);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(4);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3', 'listener5']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener3', 'listener5']);
|
|
|
|
button.removeEventListener('click', listener1);
|
|
button.removeEventListener('click', listener3);
|
|
button.removeEventListener('click', listener5);
|
|
});
|
|
|
|
it('should be able to remove part of beforeward and afterward eventListener during eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeEventListener('click', listener2, true);
|
|
button.removeEventListener('click', listener4, true);
|
|
},
|
|
};
|
|
const listener4 = function () {
|
|
logs.push('listener4');
|
|
};
|
|
const listener5 = function () {
|
|
logs.push('listener5');
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
button.addEventListener('click', listener4, true);
|
|
button.addEventListener('click', listener5, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(4);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3', 'listener5']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener3', 'listener5']);
|
|
|
|
button.removeEventListener('click', listener1, true);
|
|
button.removeEventListener('click', listener3, true);
|
|
button.removeEventListener('click', listener5, true);
|
|
});
|
|
|
|
it('should be able to remove all beforeward eventListener during eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeEventListener('click', listener1);
|
|
button.removeEventListener('click', listener2);
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['listener3']);
|
|
|
|
button.removeEventListener('click', listener3);
|
|
});
|
|
|
|
it('should be able to remove all beforeward eventListener during eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeEventListener('click', listener1, true);
|
|
button.removeEventListener('click', listener2, true);
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['listener3']);
|
|
|
|
button.removeEventListener('click', listener3, true);
|
|
});
|
|
|
|
it('should be able to remove part of beforeward eventListener during eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeEventListener('click', listener1);
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener2', 'listener3']);
|
|
|
|
button.removeEventListener('click', listener2);
|
|
button.removeEventListener('click', listener3);
|
|
});
|
|
|
|
it('should be able to remove part of beforeward eventListener during eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeEventListener('click', listener1, true);
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener2', 'listener3']);
|
|
|
|
button.removeEventListener('click', listener2, true);
|
|
button.removeEventListener('click', listener3, true);
|
|
});
|
|
|
|
it('should be able to remove all eventListeners during first eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
button.removeAllListeners!('click');
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['listener1']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
});
|
|
|
|
it('should be able to remove all eventListeners during first eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
button.removeAllListeners!('click');
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(1);
|
|
expect(logs).toEqual(['listener1']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
});
|
|
|
|
it('should be able to remove all eventListeners during middle eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
button.removeAllListeners!('click');
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
});
|
|
|
|
it('should be able to remove all eventListeners during middle eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
button.removeAllListeners!('click');
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(2);
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
});
|
|
|
|
it('should be able to remove all eventListeners during last eventListener callback', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeAllListeners!('click');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
});
|
|
|
|
it('should be able to remove all eventListeners during last eventListener callback with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
button.removeAllListeners!('click');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1, true);
|
|
button.addEventListener('click', listener2, true);
|
|
button.addEventListener('click', listener3, true);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(3);
|
|
expect(logs).toEqual(['listener1', 'listener2', 'listener3']);
|
|
|
|
logs = [];
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs.length).toBe(0);
|
|
});
|
|
});
|
|
|
|
it('should be able to get eventListeners of specified event form EventTarget', function () {
|
|
const listener1 = function () {};
|
|
const listener2 = function () {};
|
|
const listener3 = {handleEvent: function (event: Event) {}};
|
|
const listener4 = function () {};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
button.addEventListener('mouseover', listener4);
|
|
|
|
const listeners = button.eventListeners!('click');
|
|
expect(listeners.length).toBe(3);
|
|
expect(listeners).toEqual([listener1, listener2, listener3]);
|
|
button.removeEventListener('click', listener1);
|
|
button.removeEventListener('click', listener2);
|
|
button.removeEventListener('click', listener3);
|
|
});
|
|
|
|
it('should be able to get all eventListeners form EventTarget without eventName', function () {
|
|
const listener1 = function () {};
|
|
const listener2 = function () {};
|
|
const listener3 = {handleEvent: function (event: Event) {}};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('mouseover', listener2);
|
|
button.addEventListener('mousehover', listener3);
|
|
|
|
const listeners = button.eventListeners!();
|
|
expect(listeners.length).toBe(3);
|
|
expect(listeners).toEqual([listener1, listener2, listener3]);
|
|
button.removeEventListener('click', listener1);
|
|
button.removeEventListener('mouseover', listener2);
|
|
button.removeEventListener('mousehover', listener3);
|
|
});
|
|
|
|
it('should be able to remove all listeners of specified event form EventTarget', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
const listener4 = function () {
|
|
logs.push('listener4');
|
|
};
|
|
const listener5 = function () {
|
|
logs.push('listener5');
|
|
};
|
|
|
|
button.addEventListener('mouseover', listener1);
|
|
button.addEventListener('mouseover', listener2);
|
|
button.addEventListener('mouseover', listener3);
|
|
button.addEventListener('click', listener4);
|
|
button.onmouseover = listener5;
|
|
expect((button as any)[Zone.__symbol__('ON_PROPERTYmouseover')]).toEqual(listener5);
|
|
|
|
button.removeAllListeners!('mouseover');
|
|
const listeners = button.eventListeners!('mouseover');
|
|
expect(listeners.length).toBe(0);
|
|
expect((button as any)[Zone.__symbol__('ON_PROPERTYmouseover')]).toBeNull();
|
|
expect(!!button.onmouseover).toBeFalsy();
|
|
|
|
const mouseEvent = document.createEvent('Event');
|
|
mouseEvent.initEvent('mouseover', true, true);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual([]);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual(['listener4']);
|
|
|
|
button.removeEventListener('click', listener4);
|
|
});
|
|
|
|
it('should be able to remove all listeners of specified event form EventTarget with capture=true', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
const listener4 = function () {
|
|
logs.push('listener4');
|
|
};
|
|
|
|
button.addEventListener('mouseover', listener1, true);
|
|
button.addEventListener('mouseover', listener2, true);
|
|
button.addEventListener('mouseover', listener3, true);
|
|
button.addEventListener('click', listener4, true);
|
|
|
|
button.removeAllListeners!('mouseover');
|
|
const listeners = button.eventListeners!('mouseover');
|
|
expect(listeners.length).toBe(0);
|
|
|
|
const mouseEvent = document.createEvent('Event');
|
|
mouseEvent.initEvent('mouseover', true, true);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual([]);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual(['listener4']);
|
|
|
|
button.removeEventListener('click', listener4);
|
|
});
|
|
|
|
it('should be able to remove all listeners of specified event form EventTarget with mixed capture', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
const listener4 = function () {
|
|
logs.push('listener4');
|
|
};
|
|
|
|
button.addEventListener('mouseover', listener1, true);
|
|
button.addEventListener('mouseover', listener2, false);
|
|
button.addEventListener('mouseover', listener3, true);
|
|
button.addEventListener('click', listener4, true);
|
|
|
|
button.removeAllListeners!('mouseover');
|
|
const listeners = button.eventListeners!('mouseover');
|
|
expect(listeners.length).toBe(0);
|
|
|
|
const mouseEvent = document.createEvent('Event');
|
|
mouseEvent.initEvent('mouseover', true, true);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual([]);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual(['listener4']);
|
|
|
|
button.removeEventListener('click', listener4);
|
|
});
|
|
|
|
it('should be able to remove all listeners of all events form EventTarget', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
const listener4 = function () {
|
|
logs.push('listener4');
|
|
};
|
|
const listener5 = function () {
|
|
logs.push('listener5');
|
|
};
|
|
|
|
button.addEventListener('mouseover', listener1);
|
|
button.addEventListener('mouseover', listener2);
|
|
button.addEventListener('mouseover', listener3);
|
|
button.addEventListener('click', listener4);
|
|
button.onmouseover = listener5;
|
|
expect((button as any)[Zone.__symbol__('ON_PROPERTYmouseover')]).toEqual(listener5);
|
|
|
|
button.removeAllListeners!();
|
|
const listeners = button.eventListeners!('mouseover');
|
|
expect(listeners.length).toBe(0);
|
|
expect((button as any)[Zone.__symbol__('ON_PROPERTYmouseover')]).toBeNull();
|
|
expect(!!button.onmouseover).toBeFalsy();
|
|
|
|
const mouseEvent = document.createEvent('Event');
|
|
mouseEvent.initEvent('mouseover', true, true);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual([]);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual([]);
|
|
});
|
|
|
|
it('should be able to remove listener which was added outside of zone ', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
const listener4 = function () {
|
|
logs.push('listener4');
|
|
};
|
|
|
|
button.addEventListener('mouseover', listener1);
|
|
(button as any)[Zone.__symbol__('addEventListener')]('mouseover', listener2);
|
|
button.addEventListener('click', listener3);
|
|
(button as any)[Zone.__symbol__('addEventListener')]('click', listener4);
|
|
|
|
button.removeEventListener('mouseover', listener1);
|
|
button.removeEventListener('mouseover', listener2);
|
|
button.removeEventListener('click', listener3);
|
|
button.removeEventListener('click', listener4);
|
|
const listeners = button.eventListeners!('mouseover');
|
|
expect(listeners.length).toBe(0);
|
|
|
|
const mouseEvent = document.createEvent('Event');
|
|
mouseEvent.initEvent('mouseover', true, true);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual([]);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual([]);
|
|
});
|
|
|
|
it('should be able to remove all listeners which were added inside of zone ', function () {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
logs.push('listener2');
|
|
};
|
|
const listener3 = {
|
|
handleEvent: function (event: Event) {
|
|
logs.push('listener3');
|
|
},
|
|
};
|
|
const listener4 = function () {
|
|
logs.push('listener4');
|
|
};
|
|
|
|
button.addEventListener('mouseover', listener1);
|
|
(button as any)[Zone.__symbol__('addEventListener')]('mouseover', listener2);
|
|
button.addEventListener('click', listener3);
|
|
(button as any)[Zone.__symbol__('addEventListener')]('click', listener4);
|
|
|
|
button.removeAllListeners!();
|
|
const listeners = button.eventListeners!('mouseover');
|
|
expect(listeners.length).toBe(0);
|
|
|
|
const mouseEvent = document.createEvent('Event');
|
|
mouseEvent.initEvent('mouseover', true, true);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual(['listener2']);
|
|
|
|
button.dispatchEvent(clickEvent);
|
|
expect(logs).toEqual(['listener2', 'listener4']);
|
|
});
|
|
|
|
xit('should re-throw the error when the only listener throw error', function (done: DoneFn) {
|
|
// override global.onerror to prevent jasmine report error
|
|
let oriWindowOnError = window.onerror;
|
|
let logs: string[] = [];
|
|
window.onerror = function (err: any) {
|
|
logs.push(err);
|
|
};
|
|
try {
|
|
const listener1 = function () {
|
|
throw new Error('test1');
|
|
};
|
|
button.addEventListener('click', listener1);
|
|
|
|
const mouseEvent = document.createEvent('MouseEvent');
|
|
mouseEvent.initEvent('click', true, true);
|
|
|
|
const unhandledRejection = (e: PromiseRejectionEvent) => {
|
|
fail('should not be here');
|
|
};
|
|
window.addEventListener('unhandledrejection', unhandledRejection);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual(['Uncaught Error: test1']);
|
|
|
|
setTimeout(() => {
|
|
expect(logs).toEqual(['Uncaught Error: test1']);
|
|
window.removeEventListener('unhandledrejection', unhandledRejection);
|
|
window.onerror = oriWindowOnError;
|
|
done();
|
|
});
|
|
} catch (e: any) {
|
|
window.onerror = oriWindowOnError;
|
|
}
|
|
});
|
|
|
|
xit('should not re-throw the error when zone onHandleError handled the error and the only listener throw error', function (done: DoneFn) {
|
|
// override global.onerror to prevent jasmine report error
|
|
let oriWindowOnError = window.onerror;
|
|
window.onerror = function () {};
|
|
try {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
throw new Error('test1');
|
|
};
|
|
const zone = Zone.current.fork({
|
|
name: 'error',
|
|
onHandleError: (delegate, curr, target, error) => {
|
|
logs.push('zone handled ' + target.name + ' ' + error.message);
|
|
return false;
|
|
},
|
|
});
|
|
|
|
zone.runGuarded(() => {
|
|
button.addEventListener('click', listener1);
|
|
});
|
|
|
|
const mouseEvent = document.createEvent('MouseEvent');
|
|
mouseEvent.initEvent('click', true, true);
|
|
|
|
const unhandledRejection = (e: PromiseRejectionEvent) => {
|
|
logs.push(e.reason.message);
|
|
};
|
|
window.addEventListener('unhandledrejection', unhandledRejection);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual(['zone handled error test1']);
|
|
|
|
setTimeout(() => {
|
|
expect(logs).toEqual(['zone handled error test1']);
|
|
window.removeEventListener('unhandledrejection', unhandledRejection);
|
|
window.onerror = oriWindowOnError;
|
|
done();
|
|
});
|
|
} catch (e: any) {
|
|
window.onerror = oriWindowOnError;
|
|
}
|
|
});
|
|
|
|
xit('should be able to continue to invoke remaining listeners even some listener throw error', async function () {
|
|
await jasmine.spyOnGlobalErrorsAsync(async (globalErrorSpy) => {
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
throw new Error('test1');
|
|
};
|
|
const listener3 = function () {
|
|
throw new Error('test2');
|
|
};
|
|
const listener4 = {
|
|
handleEvent: function () {
|
|
logs.push('listener2');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
button.addEventListener('click', listener4);
|
|
|
|
const mouseEvent = document.createEvent('MouseEvent');
|
|
mouseEvent.initEvent('click', true, true);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
});
|
|
});
|
|
|
|
xit('should be able to continue to invoke remaining listeners even some listener throw error with onHandleError zone', function (done: DoneFn) {
|
|
// override global.onerror to prevent jasmine report error
|
|
try {
|
|
const zone = Zone.current.fork({
|
|
name: 'error',
|
|
onHandleError: (delegate, curr, target, error) => {
|
|
logs.push('zone handled ' + target.name + ' ' + error.message);
|
|
return false;
|
|
},
|
|
});
|
|
let logs: string[] = [];
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
throw new Error('test1');
|
|
};
|
|
const listener3 = function () {
|
|
throw new Error('test2');
|
|
};
|
|
const listener4 = {
|
|
handleEvent: function () {
|
|
logs.push('listener2');
|
|
},
|
|
};
|
|
|
|
zone.runGuarded(() => {
|
|
button.addEventListener('click', listener1);
|
|
button.addEventListener('click', listener2);
|
|
button.addEventListener('click', listener3);
|
|
button.addEventListener('click', listener4);
|
|
});
|
|
|
|
const mouseEvent = document.createEvent('MouseEvent');
|
|
mouseEvent.initEvent('click', true, true);
|
|
|
|
const unhandledRejection = (e: PromiseRejectionEvent) => {
|
|
fail('should not be here');
|
|
};
|
|
window.addEventListener('unhandledrejection', unhandledRejection);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual([
|
|
'listener1',
|
|
'zone handled error test1',
|
|
'zone handled error test2',
|
|
'listener2',
|
|
]);
|
|
|
|
setTimeout(() => {
|
|
expect(logs).toEqual([
|
|
'listener1',
|
|
'zone handled error test1',
|
|
'zone handled error test2',
|
|
'listener2',
|
|
]);
|
|
window.removeEventListener('unhandledrejection', unhandledRejection);
|
|
done();
|
|
});
|
|
} catch (e: any) {}
|
|
});
|
|
|
|
xit('should be able to continue to invoke remaining listeners even some listener throw error in the different zones', function (done: DoneFn) {
|
|
let logs: string[] = [];
|
|
try {
|
|
const zone1 = Zone.current.fork({
|
|
name: 'zone1',
|
|
onHandleError: (delegate, curr, target, error) => {
|
|
logs.push(error.message);
|
|
return false;
|
|
},
|
|
});
|
|
const listener1 = function () {
|
|
logs.push('listener1');
|
|
};
|
|
const listener2 = function () {
|
|
throw new Error('test1');
|
|
};
|
|
const listener3 = function () {
|
|
throw new Error('test2');
|
|
};
|
|
const listener4 = {
|
|
handleEvent: function () {
|
|
logs.push('listener2');
|
|
},
|
|
};
|
|
|
|
button.addEventListener('click', listener1);
|
|
zone1.run(() => {
|
|
button.addEventListener('click', listener2);
|
|
});
|
|
button.addEventListener('click', listener3);
|
|
button.addEventListener('click', listener4);
|
|
|
|
const mouseEvent = document.createEvent('MouseEvent');
|
|
mouseEvent.initEvent('click', true, true);
|
|
|
|
const unhandledRejection = (e: PromiseRejectionEvent) => {
|
|
fail('should not be here');
|
|
};
|
|
window.addEventListener('unhandledrejection', unhandledRejection);
|
|
|
|
button.dispatchEvent(mouseEvent);
|
|
expect(logs).toEqual(['listener1', 'listener2']);
|
|
} catch (e: any) {}
|
|
});
|
|
});
|
|
|
|
// TODO: Re-enable via https://github.com/angular/angular/pull/41526
|
|
xdescribe('unhandled promise rejection', () => {
|
|
const AsyncTestZoneSpec = (Zone as any)['AsyncTestZoneSpec'];
|
|
const asyncTest = function (testFn: Function) {
|
|
return (done: Function) => {
|
|
let asyncTestZone: Zone = Zone.current.fork(
|
|
new AsyncTestZoneSpec(
|
|
done,
|
|
(error: Error) => {
|
|
fail(error);
|
|
},
|
|
'asyncTest',
|
|
),
|
|
);
|
|
asyncTestZone.run(testFn);
|
|
};
|
|
};
|
|
|
|
it(
|
|
'should support window.addEventListener(unhandledrejection)',
|
|
asyncTest(() => {
|
|
if (!promiseUnhandleRejectionSupport()) {
|
|
return;
|
|
}
|
|
(Zone as any)[zoneSymbol('ignoreConsoleErrorUncaughtError')] = true;
|
|
Zone.root.fork({name: 'promise'}).run(function () {
|
|
const listener = (evt: any) => {
|
|
window.removeEventListener('unhandledrejection', listener);
|
|
expect(evt.type).toEqual('unhandledrejection');
|
|
expect(evt.promise.constructor.name).toEqual('Promise');
|
|
expect(evt.reason.message).toBe('promise error');
|
|
};
|
|
window.addEventListener('unhandledrejection', listener);
|
|
new Promise((resolve, reject) => {
|
|
throw new Error('promise error');
|
|
});
|
|
});
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support window.addEventListener(rejectionhandled)',
|
|
asyncTest(() => {
|
|
if (!promiseUnhandleRejectionSupport()) {
|
|
return;
|
|
}
|
|
(Zone as any)[zoneSymbol('ignoreConsoleErrorUncaughtError')] = true;
|
|
Zone.root.fork({name: 'promise'}).run(function () {
|
|
const listener = (evt: any) => {
|
|
window.removeEventListener('unhandledrejection', listener);
|
|
p.catch((reason) => {});
|
|
};
|
|
window.addEventListener('unhandledrejection', listener);
|
|
|
|
const handledListener = (evt: any) => {
|
|
window.removeEventListener('rejectionhandled', handledListener);
|
|
expect(evt.type).toEqual('rejectionhandled');
|
|
expect(evt.promise.constructor.name).toEqual('Promise');
|
|
expect(evt.reason.message).toBe('promise error');
|
|
};
|
|
|
|
window.addEventListener('rejectionhandled', handledListener);
|
|
const p = new Promise((resolve, reject) => {
|
|
throw new Error('promise error');
|
|
});
|
|
});
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'should support multiple window.addEventListener(unhandledrejection)',
|
|
asyncTest(() => {
|
|
if (!promiseUnhandleRejectionSupport()) {
|
|
return;
|
|
}
|
|
(Zone as any)[zoneSymbol('ignoreConsoleErrorUncaughtError')] = true;
|
|
Zone.root.fork({name: 'promise'}).run(function () {
|
|
const listener1 = (evt: any) => {
|
|
window.removeEventListener('unhandledrejection', listener1);
|
|
expect(evt.type).toEqual('unhandledrejection');
|
|
expect(evt.promise.constructor.name).toEqual('Promise');
|
|
expect(evt.reason.message).toBe('promise error');
|
|
};
|
|
const listener2 = (evt: any) => {
|
|
window.removeEventListener('unhandledrejection', listener2);
|
|
expect(evt.type).toEqual('unhandledrejection');
|
|
expect(evt.promise.constructor.name).toEqual('Promise');
|
|
expect(evt.reason.message).toBe('promise error');
|
|
evt.preventDefault();
|
|
};
|
|
window.addEventListener('unhandledrejection', listener1);
|
|
window.addEventListener('unhandledrejection', listener2);
|
|
new Promise((resolve, reject) => {
|
|
throw new Error('promise error');
|
|
});
|
|
});
|
|
}),
|
|
);
|
|
});
|
|
|
|
// @JiaLiPassion, Edge 15, the behavior is not the same with Chrome
|
|
// wait for fix.
|
|
xit(
|
|
'IntersectionObserver should run callback in zone',
|
|
ifEnvSupportsWithDone('IntersectionObserver', (done: Function) => {
|
|
const div = document.createElement('div');
|
|
document.body.appendChild(div);
|
|
const options: any = {threshold: 0.5};
|
|
|
|
const zone = Zone.current.fork({name: 'intersectionObserverZone'});
|
|
|
|
zone.run(() => {
|
|
const observer = new IntersectionObserver(() => {
|
|
expect(Zone.current.name).toEqual(zone.name);
|
|
observer.unobserve(div);
|
|
done();
|
|
}, options);
|
|
observer.observe(div);
|
|
});
|
|
div.style.display = 'none';
|
|
div.style.visibility = 'block';
|
|
}),
|
|
);
|
|
|
|
it(
|
|
'HTMLCanvasElement.toBlob should be a ZoneAware MacroTask',
|
|
ifEnvSupportsWithDone(supportCanvasTest, (done: Function) => {
|
|
const canvas = document.createElement('canvas');
|
|
const d = canvas.width;
|
|
const ctx = canvas.getContext('2d')!;
|
|
ctx.beginPath();
|
|
ctx.moveTo(d / 2, 0);
|
|
ctx.lineTo(d, d);
|
|
ctx.lineTo(0, d);
|
|
ctx.closePath();
|
|
ctx.fillStyle = 'yellow';
|
|
ctx.fill();
|
|
|
|
const scheduleSpy = jasmine.createSpy('scheduleSpy');
|
|
const zone: Zone = Zone.current.fork({
|
|
name: 'canvas',
|
|
onScheduleTask: (
|
|
delegate: ZoneDelegate,
|
|
currentZone: Zone,
|
|
targetZone: Zone,
|
|
task: Task,
|
|
) => {
|
|
scheduleSpy();
|
|
return delegate.scheduleTask(targetZone, task);
|
|
},
|
|
});
|
|
|
|
zone.run(() => {
|
|
const canvasData = canvas.toDataURL();
|
|
canvas.toBlob(function (blob) {
|
|
expect(Zone.current.name).toEqual('canvas');
|
|
expect(scheduleSpy).toHaveBeenCalled();
|
|
|
|
const reader = new FileReader();
|
|
reader.readAsDataURL(blob!);
|
|
reader.onloadend = function () {
|
|
const base64data = reader.result;
|
|
expect(base64data).toEqual(canvasData);
|
|
done();
|
|
};
|
|
});
|
|
});
|
|
}),
|
|
);
|
|
|
|
describe(
|
|
'ResizeObserver',
|
|
ifEnvSupports('ResizeObserver', () => {
|
|
it('ResizeObserver callback should be in zone', (done) => {
|
|
const ResizeObserver = (window as any)['ResizeObserver'];
|
|
const div = document.createElement('div');
|
|
const zone = Zone.current.fork({name: 'observer'});
|
|
const observer = new ResizeObserver((entries: any, ob: any) => {
|
|
expect(Zone.current.name).toEqual(zone.name);
|
|
|
|
expect(entries.length).toBe(1);
|
|
expect(entries[0].target).toBe(div);
|
|
observer.disconnect();
|
|
done();
|
|
});
|
|
|
|
zone.run(() => {
|
|
observer.observe(div);
|
|
});
|
|
|
|
document.body.appendChild(div);
|
|
});
|
|
|
|
it('ResizeObserver callback should be able to in different zones which when they were observed', (done) => {
|
|
const ResizeObserver = (window as any)['ResizeObserver'];
|
|
const div1 = document.createElement('div');
|
|
const div2 = document.createElement('div');
|
|
const zone = Zone.current.fork({name: 'observer'});
|
|
let count = 0;
|
|
const observer = new ResizeObserver((entries: any, ob: any) => {
|
|
entries.forEach((entry: any) => {
|
|
if (entry.target === div1) {
|
|
expect(Zone.current.name).toEqual(zone.name);
|
|
} else {
|
|
expect(Zone.current.name).toEqual('<root>');
|
|
}
|
|
});
|
|
count++;
|
|
if (count === 2) {
|
|
done();
|
|
}
|
|
});
|
|
|
|
zone.run(() => {
|
|
observer.observe(div1);
|
|
});
|
|
Zone.root.run(() => {
|
|
observer.observe(div2);
|
|
});
|
|
|
|
document.body.appendChild(div1);
|
|
document.body.appendChild(div2);
|
|
});
|
|
}),
|
|
);
|
|
|
|
xdescribe('getUserMedia', () => {
|
|
it(
|
|
'navigator.mediaDevices.getUserMedia should in zone',
|
|
ifEnvSupportsWithDone(
|
|
() => {
|
|
return (
|
|
navigator &&
|
|
navigator.mediaDevices &&
|
|
typeof navigator.mediaDevices.getUserMedia === 'function'
|
|
);
|
|
},
|
|
(done: Function) => {
|
|
const zone = Zone.current.fork({name: 'media'});
|
|
zone.run(() => {
|
|
const constraints = {audio: true, video: {width: 1280, height: 720}};
|
|
|
|
navigator.mediaDevices
|
|
.getUserMedia(constraints)
|
|
.then(function (mediaStream) {
|
|
expect(Zone.current.name).toEqual(zone.name);
|
|
done();
|
|
})
|
|
.catch(function (err) {
|
|
console.log(err.name + ': ' + err.message);
|
|
expect(Zone.current.name).toEqual(zone.name);
|
|
done();
|
|
});
|
|
});
|
|
},
|
|
),
|
|
);
|
|
|
|
// Note: `navigator` is cast to `any` in this test, because the preferred way of accessing
|
|
// `getUserMedia` is through `navigator.mediaDevices`, however some older browsers still
|
|
// expose it directly on `navigator`.
|
|
it(
|
|
'navigator.getUserMedia should in zone',
|
|
ifEnvSupportsWithDone(
|
|
() => {
|
|
return navigator && typeof (navigator as any).getUserMedia === 'function';
|
|
},
|
|
(done: Function) => {
|
|
const zone = Zone.current.fork({name: 'media'});
|
|
zone.run(() => {
|
|
const constraints = {audio: true, video: {width: 1280, height: 720}};
|
|
(navigator as any).getUserMedia(
|
|
constraints,
|
|
() => {
|
|
expect(Zone.current.name).toEqual(zone.name);
|
|
done();
|
|
},
|
|
() => {
|
|
expect(Zone.current.name).toEqual(zone.name);
|
|
done();
|
|
},
|
|
);
|
|
});
|
|
},
|
|
),
|
|
);
|
|
});
|
|
});
|
|
});
|