build: move zone.js to angular repo (#30962)

PR Close #30962
This commit is contained in:
JiaLiPassion
2019-06-01 00:56:07 +09:00
committed by Kara Erickson
parent 7b3bcc23af
commit 5eb7426216
271 changed files with 30890 additions and 19 deletions

View File

@ -0,0 +1,425 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {isBrowser} from '../../lib/common/utils';
import {isSafari, zoneSymbol} from '../test-util';
// simulate @angular/facade/src/error.ts
class BaseError extends Error {
/** @internal **/
_nativeError: Error;
constructor(message: string) {
super(message);
const nativeError = new Error(message) as any as Error;
this._nativeError = nativeError;
}
get message() { return this._nativeError.message; }
set message(message) { this._nativeError.message = message; }
get name() { return this._nativeError.name; }
get stack() { return (this._nativeError as any).stack; }
set stack(value) { (this._nativeError as any).stack = value; }
toString() { return this._nativeError.toString(); }
}
class WrappedError extends BaseError {
originalError: any;
constructor(message: string, error: any) {
super(`${message} caused by: ${error instanceof Error ? error.message : error}`);
this.originalError = error;
}
get stack() {
return ((this.originalError instanceof Error ? this.originalError : this._nativeError) as any)
.stack;
}
}
class TestError extends WrappedError {
constructor(message: string, error: any) {
super(`${message} caused by: ${error instanceof Error ? error.message : error}`, error);
}
get message() { return 'test ' + this.originalError.message; }
}
class TestMessageError extends WrappedError {
constructor(message: string, error: any) {
super(`${message} caused by: ${error instanceof Error ? error.message : error}`, error);
}
get message() { return 'test ' + this.originalError.message; }
set message(value) { this.originalError.message = value; }
}
describe('ZoneAwareError', () => {
// If the environment does not supports stack rewrites, then these tests will fail
// and there is no point in running them.
const _global: any = typeof window !== 'undefined' ? window : global;
let config: any;
const __karma__ = _global.__karma__;
if (typeof __karma__ !== 'undefined') {
config = __karma__ && (__karma__ as any).config;
} else if (typeof process !== 'undefined') {
config = process.env;
}
const policy = (config && config['errorpolicy']) || 'default';
if (!(Error as any)['stackRewrite'] && policy !== 'disable') return;
it('should keep error prototype chain correctly', () => {
class MyError extends Error {}
const myError = new MyError();
expect(myError instanceof Error).toBe(true);
expect(myError instanceof MyError).toBe(true);
expect(myError.stack).not.toBe(undefined);
});
it('should instanceof error correctly', () => {
let myError = Error('myError');
expect(myError instanceof Error).toBe(true);
let myError1 = Error.call(undefined, 'myError');
expect(myError1 instanceof Error).toBe(true);
let myError2 = Error.call(global, 'myError');
expect(myError2 instanceof Error).toBe(true);
let myError3 = Error.call({}, 'myError');
expect(myError3 instanceof Error).toBe(true);
let myError4 = Error.call({test: 'test'}, 'myError');
expect(myError4 instanceof Error).toBe(true);
});
it('should return error itself from constructor', () => {
class MyError1 extends Error {
constructor() {
const err: any = super('MyError1');
this.message = err.message;
}
}
let myError1 = new MyError1();
expect(myError1.message).toEqual('MyError1');
expect(myError1.name).toEqual('Error');
});
it('should return error by calling error directly', () => {
let myError = Error('myError');
expect(myError.message).toEqual('myError');
let myError1 = Error.call(undefined, 'myError');
expect(myError1.message).toEqual('myError');
let myError2 = Error.call(global, 'myError');
expect(myError2.message).toEqual('myError');
let myError3 = Error.call({}, 'myError');
expect(myError3.message).toEqual('myError');
});
it('should have browser specified property', () => {
let myError = new Error('myError');
if (Object.prototype.hasOwnProperty.call(Error.prototype, 'description')) {
// in IE, error has description property
expect((<any>myError).description).toEqual('myError');
}
if (Object.prototype.hasOwnProperty.call(Error.prototype, 'fileName')) {
// in firefox, error has fileName property
expect((<any>myError).fileName).toBeTruthy();
}
});
it('should not use child Error class get/set in ZoneAwareError constructor', () => {
const func = () => {
const error = new BaseError('test');
expect(error.message).toEqual('test');
};
expect(func).not.toThrow();
});
it('should behave correctly with wrapped error', () => {
const error = new TestError('originalMessage', new Error('error message'));
expect(error.message).toEqual('test error message');
error.originalError.message = 'new error message';
expect(error.message).toEqual('test new error message');
const error1 = new TestMessageError('originalMessage', new Error('error message'));
expect(error1.message).toEqual('test error message');
error1.message = 'new error message';
expect(error1.message).toEqual('test new error message');
});
it('should copy customized NativeError properties to ZoneAwareError', () => {
const spy = jasmine.createSpy('errorCustomFunction');
const NativeError = (global as any)[(Zone as any).__symbol__('Error')];
NativeError.customFunction = function(args: any) { spy(args); };
expect((Error as any)['customProperty']).toBe('customProperty');
expect(typeof(Error as any)['customFunction']).toBe('function');
(Error as any)['customFunction']('test');
expect(spy).toHaveBeenCalledWith('test');
});
it('should always have stack property even without throw', () => {
// in IE, the stack will be undefined without throw
// in ZoneAwareError, we will make stack always be
// there event without throw
const error = new Error('test');
const errorWithoutNew = Error('test');
expect(error.stack !.split('\n').length > 0).toBeTruthy();
expect(errorWithoutNew.stack !.split('\n').length > 0).toBeTruthy();
});
it('should show zone names in stack frames and remove extra frames', () => {
if (policy === 'disable' || !(Error as any)['stackRewrite']) {
return;
}
if (isBrowser && isSafari()) {
return;
}
const rootZone = Zone.root;
const innerZone = rootZone.fork({name: 'InnerZone'});
rootZone.run(testFn);
function testFn() {
let outside: any;
let inside: any;
let outsideWithoutNew: any;
let insideWithoutNew: any;
try {
throw new Error('Outside');
} catch (e) {
outside = e;
}
try {
throw Error('Outside');
} catch (e) {
outsideWithoutNew = e;
}
innerZone.run(function insideRun() {
try {
throw new Error('Inside');
} catch (e) {
inside = e;
}
try {
throw Error('Inside');
} catch (e) {
insideWithoutNew = e;
}
});
if (policy === 'lazy') {
outside.stack = outside.zoneAwareStack;
outsideWithoutNew.stack = outsideWithoutNew.zoneAwareStack;
inside.stack = inside.zoneAwareStack;
insideWithoutNew.stack = insideWithoutNew.zoneAwareStack;
}
expect(outside.stack).toEqual(outside.zoneAwareStack);
expect(outsideWithoutNew.stack).toEqual(outsideWithoutNew.zoneAwareStack);
expect(inside !.stack).toEqual(inside !.zoneAwareStack);
expect(insideWithoutNew !.stack).toEqual(insideWithoutNew !.zoneAwareStack);
expect(typeof inside !.originalStack).toEqual('string');
expect(typeof insideWithoutNew !.originalStack).toEqual('string');
const outsideFrames = outside.stack !.split(/\n/);
const insideFrames = inside !.stack !.split(/\n/);
const outsideWithoutNewFrames = outsideWithoutNew !.stack !.split(/\n/);
const insideWithoutNewFrames = insideWithoutNew !.stack !.split(/\n/);
// throw away first line if it contains the error
if (/Outside/.test(outsideFrames[0])) {
outsideFrames.shift();
}
if (/Error /.test(outsideFrames[0])) {
outsideFrames.shift();
}
if (/Outside/.test(outsideWithoutNewFrames[0])) {
outsideWithoutNewFrames.shift();
}
if (/Error /.test(outsideWithoutNewFrames[0])) {
outsideWithoutNewFrames.shift();
}
if (/Inside/.test(insideFrames[0])) {
insideFrames.shift();
}
if (/Error /.test(insideFrames[0])) {
insideFrames.shift();
}
if (/Inside/.test(insideWithoutNewFrames[0])) {
insideWithoutNewFrames.shift();
}
if (/Error /.test(insideWithoutNewFrames[0])) {
insideWithoutNewFrames.shift();
}
expect(outsideFrames[0]).toMatch(/testFn.*[<root>]/);
expect(insideFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
expect(insideFrames[1]).toMatch(/testFn.*[<root>]]/);
expect(outsideWithoutNewFrames[0]).toMatch(/testFn.*[<root>]/);
expect(insideWithoutNewFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
expect(insideWithoutNewFrames[1]).toMatch(/testFn.*[<root>]]/);
}
});
const zoneAwareFrames = [
'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask',
'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask',
'ZoneDelegate.invokeTask', 'zoneAwareAddListener', 'Zone.prototype.run',
'Zone.prototype.runGuarded', 'Zone.prototype.scheduleEventTask',
'Zone.prototype.scheduleMicroTask', 'Zone.prototype.scheduleMacroTask',
'Zone.prototype.runTask', 'ZoneDelegate.prototype.scheduleTask',
'ZoneDelegate.prototype.invokeTask', 'ZoneTask.invokeTask'
];
function assertStackDoesNotContainZoneFrames(err: any) {
const frames = policy === 'lazy' ? err.zoneAwareStack.split('\n') : err.stack.split('\n');
if (policy === 'disable') {
let hasZoneStack = false;
for (let i = 0; i < frames.length; i++) {
if (hasZoneStack) {
break;
}
hasZoneStack = zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1).length > 0;
}
if (!hasZoneStack) {
console.log('stack', err.originalStack);
}
expect(hasZoneStack).toBe(true);
} else {
for (let i = 0; i < frames.length; i++) {
expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]);
}
}
};
const errorZoneSpec = {
name: 'errorZone',
done: <(() => void)|null>null,
onHandleError:
(parentDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: Error) => {
assertStackDoesNotContainZoneFrames(error);
setTimeout(() => { errorZoneSpec.done && errorZoneSpec.done(); }, 0);
return false;
}
};
const errorZone = Zone.root.fork(errorZoneSpec);
const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) {
return function(done: () => void) {
errorZoneSpec.done = done;
errorZone.run(testFn);
};
};
describe('Error stack', () => {
it('Error with new which occurs in setTimeout callback should not have zone frames visible',
assertStackDoesNotContainZoneFramesTest(
() => { setTimeout(() => { throw new Error('timeout test error'); }, 10); }));
it('Error without new which occurs in setTimeout callback should not have zone frames visible',
assertStackDoesNotContainZoneFramesTest(
() => { setTimeout(() => { throw Error('test error'); }, 10); }));
it('Error with new which cause by promise rejection should not have zone frames visible',
(done) => {
const p = new Promise(
(resolve, reject) => { setTimeout(() => { reject(new Error('test error')); }); });
p.catch(err => {
assertStackDoesNotContainZoneFrames(err);
done();
});
});
it('Error without new which cause by promise rejection should not have zone frames visible',
(done) => {
const p = new Promise(
(resolve, reject) => { setTimeout(() => { reject(Error('test error')); }); });
p.catch(err => {
assertStackDoesNotContainZoneFrames(err);
done();
});
});
it('Error with new which occurs in eventTask callback should not have zone frames visible',
assertStackDoesNotContainZoneFramesTest(() => {
const task = Zone.current.scheduleEventTask('errorEvent', () => {
throw new Error('test error');
}, undefined, () => null, undefined);
task.invoke();
}));
it('Error without new which occurs in eventTask callback should not have zone frames visible',
assertStackDoesNotContainZoneFramesTest(() => {
const task = Zone.current.scheduleEventTask(
'errorEvent', () => { throw Error('test error'); }, undefined, () => null, undefined);
task.invoke();
}));
it('Error with new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
assertStackDoesNotContainZoneFramesTest(() => {
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
.scheduleEventTask('errorEvent', () => {
throw new Error('test error');
}, undefined, () => null, undefined);
task.invoke();
}));
it('Error without new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
assertStackDoesNotContainZoneFramesTest(() => {
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
.scheduleEventTask('errorEvent', () => {
throw Error('test error');
}, undefined, () => null, undefined);
task.invoke();
}));
it('stack frames of the callback in user customized zoneSpec should be kept',
assertStackDoesNotContainZoneFramesTest(() => {
const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
.fork({
name: 'customZone',
onScheduleTask: (parentDelegate, currentZone, targetZone, task) => {
return parentDelegate.scheduleTask(targetZone, task);
},
onHandleError: (parentDelegate, currentZone, targetZone, error) => {
parentDelegate.handleError(targetZone, error);
const containsCustomZoneSpecStackTrace =
error.stack.indexOf('onScheduleTask') !== -1;
expect(containsCustomZoneSpecStackTrace).toBeTruthy();
return false;
}
})
.scheduleEventTask('errorEvent', () => {
throw new Error('test error');
}, undefined, () => null, undefined);
task.invoke();
}));
it('should be able to generate zone free stack even NativeError stack is readonly', function() {
const _global: any =
typeof window === 'object' && window || typeof self === 'object' && self || global;
const NativeError = _global[zoneSymbol('Error')];
const desc = Object.getOwnPropertyDescriptor(NativeError.prototype, 'stack');
if (desc) {
const originalSet: ((value: any) => void)|undefined = desc.set;
// make stack readonly
desc.set = null as any;
try {
const error = new Error('test error');
expect(error.stack).toBeTruthy();
assertStackDoesNotContainZoneFrames(error);
} finally {
desc.set = originalSet;
}
}
});
});
});

View File

@ -0,0 +1,521 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {isNode, zoneSymbol} from '../../lib/common/utils';
import {ifEnvSupports} from '../test-util';
declare const global: any;
class MicroTaskQueueZoneSpec implements ZoneSpec {
name: string = 'MicroTaskQueue';
queue: MicroTask[] = [];
properties = {queue: this.queue, flush: this.flush.bind(this)};
flush() {
while (this.queue.length) {
const task = this.queue.shift();
task !.invoke();
}
}
onScheduleTask(delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, task: Task): any {
this.queue.push(task as MicroTask);
}
}
function flushMicrotasks() {
Zone.current.get('flush')();
}
class TestRejection {
prop1?: string;
prop2?: string;
}
describe(
'Promise', ifEnvSupports('Promise', function() {
if (!global.Promise) return;
let log: string[];
let queueZone: Zone;
let testZone: Zone;
let pZone: Zone;
beforeEach(() => {
testZone = Zone.current.fork({name: 'TestZone'});
pZone = Zone.current.fork({
name: 'promise-zone',
onScheduleTask: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
task: Task): any => {
log.push('scheduleTask');
parentZoneDelegate.scheduleTask(targetZone, task);
}
});
queueZone = Zone.current.fork(new MicroTaskQueueZoneSpec());
log = [];
});
xit('should allow set es6 Promise after load ZoneAwarePromise', (done) => {
const ES6Promise = require('es6-promise').Promise;
const NativePromise = global[zoneSymbol('Promise')];
try {
global['Promise'] = ES6Promise;
Zone.assertZonePatched();
expect(global[zoneSymbol('Promise')]).toBe(ES6Promise);
const promise = Promise.resolve(0);
console.log('promise', promise);
promise
.then(value => {
expect(value).toBe(0);
done();
})
.catch(error => { fail(error); });
} finally {
global['Promise'] = NativePromise;
Zone.assertZonePatched();
expect(global[zoneSymbol('Promise')]).toBe(NativePromise);
}
});
it('should pretend to be a native code',
() => { expect(String(Promise).indexOf('[native code]') >= 0).toBe(true); });
it('should use native toString for promise instance', () => {
expect(Object.prototype.toString.call(Promise.resolve())).toEqual('[object Promise]');
});
it('should make sure that new Promise is instance of Promise', () => {
expect(Promise.resolve(123) instanceof Promise).toBe(true);
expect(new Promise(() => null) instanceof Promise).toBe(true);
});
xit('should ensure that Promise this is instanceof Promise', () => {
expect(() => {
Promise.call({}, () => null);
}).toThrowError('Must be an instanceof Promise.');
});
xit('should allow subclassing', () => {
class MyPromise extends Promise<any> {
constructor(fn: any) { super(fn); }
}
expect(new MyPromise(null).then(() => null) instanceof MyPromise).toBe(true);
});
it('should intercept scheduling of resolution and then', (done) => {
pZone.run(() => {
let p: Promise<any> =
new Promise(function(resolve, reject) { expect(resolve('RValue')).toBe(undefined); });
expect(log).toEqual([]);
expect(p instanceof Promise).toBe(true);
p = p.then((v) => {
log.push(v);
expect(v).toBe('RValue');
expect(log).toEqual(['scheduleTask', 'RValue']);
return 'second value';
});
expect(p instanceof Promise).toBe(true);
expect(log).toEqual(['scheduleTask']);
p = p.then((v) => {
log.push(v);
expect(log).toEqual(['scheduleTask', 'RValue', 'scheduleTask', 'second value']);
done();
});
expect(p instanceof Promise).toBe(true);
expect(log).toEqual(['scheduleTask']);
});
});
it('should allow sync resolution of promises', () => {
queueZone.run(() => {
const flush = Zone.current.get('flush');
const queue = Zone.current.get('queue');
const p = new Promise<string>(function(resolve, reject) { resolve('RValue'); })
.then((v: string) => {
log.push(v);
return 'second value';
})
.then((v: string) => { log.push(v); });
expect(queue.length).toEqual(1);
expect(log).toEqual([]);
flush();
expect(log).toEqual(['RValue', 'second value']);
});
});
it('should allow sync resolution of promises returning promises', () => {
queueZone.run(() => {
const flush = Zone.current.get('flush');
const queue = Zone.current.get('queue');
const p =
new Promise<string>(function(resolve, reject) { resolve(Promise.resolve('RValue')); })
.then((v: string) => {
log.push(v);
return Promise.resolve('second value');
})
.then((v: string) => { log.push(v); });
expect(queue.length).toEqual(1);
expect(log).toEqual([]);
flush();
expect(log).toEqual(['RValue', 'second value']);
});
});
describe('Promise API', function() {
it('should work with .then', function(done) {
let resolve: Function|null = null;
testZone.run(function() {
new Promise(function(resolveFn) { resolve = resolveFn; }).then(function() {
expect(Zone.current).toBe(testZone);
done();
});
});
resolve !();
});
it('should work with .catch', function(done) {
let reject: (() => void)|null = null;
testZone.run(function() {
new Promise(function(resolveFn, rejectFn) { reject = rejectFn; })['catch'](function() {
expect(Zone.current).toBe(testZone);
done();
});
});
expect(reject !()).toBe(undefined);
});
it('should work with .finally with resolved promise', function(done) {
let resolve: Function|null = null;
testZone.run(function() {
(new Promise(function(resolveFn) { resolve = resolveFn; }) as any).finally(function() {
expect(arguments.length).toBe(0);
expect(Zone.current).toBe(testZone);
done();
});
});
resolve !('value');
});
it('should work with .finally with rejected promise', function(done) {
let reject: Function|null = null;
testZone.run(function() {
(new Promise(function(_, rejectFn) { reject = rejectFn; }) as any).finally(function() {
expect(arguments.length).toBe(0);
expect(Zone.current).toBe(testZone);
done();
});
});
reject !('error');
});
it('should work with Promise.resolve', () => {
queueZone.run(() => {
let value: any = null;
Promise.resolve('resolveValue').then((v) => value = v);
expect(Zone.current.get('queue').length).toEqual(1);
flushMicrotasks();
expect(value).toEqual('resolveValue');
});
});
it('should work with Promise.reject', () => {
queueZone.run(() => {
let value: any = null;
Promise.reject('rejectReason')['catch']((v) => value = v);
expect(Zone.current.get('queue').length).toEqual(1);
flushMicrotasks();
expect(value).toEqual('rejectReason');
});
});
describe('reject', () => {
it('should reject promise', () => {
queueZone.run(() => {
let value: any = null;
Promise.reject('rejectReason')['catch']((v) => value = v);
flushMicrotasks();
expect(value).toEqual('rejectReason');
});
});
it('should re-reject promise', () => {
queueZone.run(() => {
let value: any = null;
Promise.reject('rejectReason')['catch']((v) => { throw v; })['catch'](
(v) => value = v);
flushMicrotasks();
expect(value).toEqual('rejectReason');
});
});
it('should reject and recover promise', () => {
queueZone.run(() => {
let value: any = null;
Promise.reject('rejectReason')['catch']((v) => v).then((v) => value = v);
flushMicrotasks();
expect(value).toEqual('rejectReason');
});
});
it('should reject if chained promise does not catch promise', () => {
queueZone.run(() => {
let value: any = null;
Promise.reject('rejectReason')
.then((v) => fail('should not get here'))
.then(null, (v) => value = v);
flushMicrotasks();
expect(value).toEqual('rejectReason');
});
});
it('should output error to console if ignoreConsoleErrorUncaughtError is false',
(done) => {
Zone.current.fork({name: 'promise-error'}).run(() => {
(Zone as any)[Zone.__symbol__('ignoreConsoleErrorUncaughtError')] = false;
const originalConsoleError = console.error;
console.error = jasmine.createSpy('consoleErr');
const p = new Promise((resolve, reject) => { throw new Error('promise error'); });
setTimeout(() => {
expect(console.error).toHaveBeenCalled();
console.error = originalConsoleError;
done();
}, 10);
});
});
it('should not output error to console if ignoreConsoleErrorUncaughtError is true',
(done) => {
Zone.current.fork({name: 'promise-error'}).run(() => {
(Zone as any)[Zone.__symbol__('ignoreConsoleErrorUncaughtError')] = true;
const originalConsoleError = console.error;
console.error = jasmine.createSpy('consoleErr');
const p = new Promise((resolve, reject) => { throw new Error('promise error'); });
setTimeout(() => {
expect(console.error).not.toHaveBeenCalled();
console.error = originalConsoleError;
(Zone as any)[Zone.__symbol__('ignoreConsoleErrorUncaughtError')] = false;
done();
}, 10);
});
});
it('should notify Zone.onHandleError if no one catches promise', (done) => {
let promiseError: Error|null = null;
let zone: Zone|null = null;
let task: Task|null = null;
let error: Error|null = null;
queueZone
.fork({
name: 'promise-error',
onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any):
boolean => {
promiseError = error;
delegate.handleError(target, error);
return false;
}
})
.run(() => {
zone = Zone.current;
task = Zone.currentTask;
error = new Error('rejectedErrorShouldBeHandled');
try {
// throw so that the stack trace is captured
throw error;
} catch (e) {
}
Promise.reject(error);
expect(promiseError).toBe(null);
});
setTimeout((): any => null);
setTimeout(() => {
expect(promiseError !.message)
.toBe(
'Uncaught (in promise): ' + error +
(error !.stack ? '\n' + error !.stack : ''));
expect((promiseError as any)['rejection']).toBe(error);
expect((promiseError as any)['zone']).toBe(zone);
expect((promiseError as any)['task']).toBe(task);
done();
});
});
it('should print readable information when throw a not error object', (done) => {
let promiseError: Error|null = null;
let zone: Zone|null = null;
let task: Task|null = null;
let rejectObj: TestRejection;
queueZone
.fork({
name: 'promise-error',
onHandleError: (delegate: ZoneDelegate, current: Zone, target: Zone, error: any):
boolean => {
promiseError = error;
delegate.handleError(target, error);
return false;
}
})
.run(() => {
zone = Zone.current;
task = Zone.currentTask;
rejectObj = new TestRejection();
rejectObj.prop1 = 'value1';
rejectObj.prop2 = 'value2';
Promise.reject(rejectObj);
expect(promiseError).toBe(null);
});
setTimeout((): any => null);
setTimeout(() => {
expect(promiseError !.message)
.toMatch(/Uncaught \(in promise\):.*: {"prop1":"value1","prop2":"value2"}/);
done();
});
});
});
describe('Promise.race', () => {
it('should reject the value', () => {
queueZone.run(() => {
let value: any = null;
(Promise as any).race([
Promise.reject('rejection1'), 'v1'
])['catch']((v: any) => value = v);
// expect(Zone.current.get('queue').length).toEqual(2);
flushMicrotasks();
expect(value).toEqual('rejection1');
});
});
it('should resolve the value', () => {
queueZone.run(() => {
let value: any = null;
(Promise as any)
.race([Promise.resolve('resolution'), 'v1'])
.then((v: any) => value = v);
// expect(Zone.current.get('queue').length).toEqual(2);
flushMicrotasks();
expect(value).toEqual('resolution');
});
});
});
describe('Promise.all', () => {
it('should reject the value', () => {
queueZone.run(() => {
let value: any = null;
Promise.all([Promise.reject('rejection'), 'v1'])['catch']((v: any) => value = v);
// expect(Zone.current.get('queue').length).toEqual(2);
flushMicrotasks();
expect(value).toEqual('rejection');
});
});
it('should resolve the value', () => {
queueZone.run(() => {
let value: any = null;
Promise.all([Promise.resolve('resolution'), 'v1']).then((v: any) => value = v);
// expect(Zone.current.get('queue').length).toEqual(2);
flushMicrotasks();
expect(value).toEqual(['resolution', 'v1']);
});
});
it('should resolve with the sync then operation', () => {
queueZone.run(() => {
let value: any = null;
const p1 = {then: function(thenCallback: Function) { return thenCallback('p1'); }};
const p2 = {then: function(thenCallback: Function) { return thenCallback('p2'); }};
Promise.all([p1, 'v1', p2]).then((v: any) => value = v);
// expect(Zone.current.get('queue').length).toEqual(2);
flushMicrotasks();
expect(value).toEqual(['p1', 'v1', 'p2']);
});
});
it('should resolve generators',
ifEnvSupports(
() => { return isNode; },
() => {
const generators: any = function* () {
yield Promise.resolve(1);
yield Promise.resolve(2);
return;
};
queueZone.run(() => {
let value: any = null;
Promise.all(generators()).then(val => { value = val; });
// expect(Zone.current.get('queue').length).toEqual(2);
flushMicrotasks();
expect(value).toEqual([1, 2]);
});
}));
});
});
describe('Promise subclasses', function() {
class MyPromise<T> {
private _promise: Promise<any>;
constructor(init: any) { this._promise = new Promise(init); }
catch<TResult = never>(onrejected?: ((reason: any) => TResult | PromiseLike<TResult>)|
undefined|null): Promise<T|TResult> {
return this._promise.catch.call(this._promise, onrejected);
};
then<TResult1 = T, TResult2 = never>(
onfulfilled?: ((value: T) => TResult1 | PromiseLike<TResult1>)|undefined|null,
onrejected?: ((reason: any) => TResult2 | PromiseLike<TResult2>)|undefined|
null): Promise<any> {
return this._promise.then.call(this._promise, onfulfilled, onrejected);
};
}
const setPrototypeOf = (Object as any).setPrototypeOf || function(obj: any, proto: any) {
obj.__proto__ = proto;
return obj;
};
setPrototypeOf(MyPromise.prototype, Promise.prototype);
it('should reject if the Promise subclass rejects', function() {
const myPromise =
new MyPromise(function(resolve: any, reject: any): void { reject('foo'); });
return Promise.resolve()
.then(function() { return myPromise; })
.then(
function() { throw new Error('Unexpected resolution'); },
function(result) { expect(result).toBe('foo'); });
});
function testPromiseSubClass(done?: Function) {
const myPromise =
new MyPromise(function(resolve: any, reject: Function) { resolve('foo'); });
return Promise.resolve().then(function() { return myPromise; }).then(function(result) {
expect(result).toBe('foo');
done && done();
});
}
it('should resolve if the Promise subclass resolves', jasmine ? function(done) {
testPromiseSubClass(done);
} : function() { testPromiseSubClass(); });
});
}));

View File

@ -0,0 +1,205 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {ifEnvSupports, ifEnvSupportsWithDone, isFirefox, isSafari} from '../test-util';
declare const global: any;
describe(
'fetch', ifEnvSupports('fetch', function() {
let testZone: Zone;
beforeEach(() => { testZone = Zone.current.fork({name: 'TestZone'}); });
it('should work for text response', function(done) {
testZone.run(function() {
global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
.then(function(response: any) {
const fetchZone = Zone.current;
expect(fetchZone.name).toBe(testZone.name);
response.text().then(function(text: string) {
expect(Zone.current.name).toBe(fetchZone.name);
expect(text.trim()).toEqual('{"hello": "world"}');
done();
});
});
});
});
it('should work for json response', function(done) {
testZone.run(function() {
global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
.then(function(response: any) {
const fetchZone = Zone.current;
expect(fetchZone.name).toBe(testZone.name);
response.json().then(function(obj: any) {
expect(Zone.current.name).toBe(fetchZone.name);
expect(obj.hello).toEqual('world');
done();
});
});
});
});
it('should work for blob response', function(done) {
testZone.run(function() {
global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
.then(function(response: any) {
const fetchZone = Zone.current;
expect(fetchZone.name).toBe(testZone.name);
// Android 4.3- doesn't support response.blob()
if (response.blob) {
response.blob().then(function(blob: any) {
expect(Zone.current.name).toBe(fetchZone.name);
expect(blob instanceof Blob).toEqual(true);
done();
});
} else {
done();
}
});
});
});
it('should work for arrayBuffer response', function(done) {
testZone.run(function() {
global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
.then(function(response: any) {
const fetchZone = Zone.current;
expect(fetchZone.name).toBe(testZone.name);
// Android 4.3- doesn't support response.arrayBuffer()
if (response.arrayBuffer) {
response.arrayBuffer().then(function(blob: any) {
expect(Zone.current).toBe(fetchZone);
expect(blob instanceof ArrayBuffer).toEqual(true);
done();
});
} else {
done();
}
});
});
});
it('should throw error when send crendential',
ifEnvSupportsWithDone(isFirefox, function(done: DoneFn) {
testZone.run(function() {
global['fetch']('http://user:password@example.com')
.then(
function(response: any) { fail('should not success'); },
(error: any) => {
expect(Zone.current.name).toEqual(testZone.name);
expect(error.constructor.name).toEqual('TypeError');
done();
});
});
}));
describe('macroTask', () => {
const logs: string[] = [];
let fetchZone: Zone;
let fetchTask: any = null;
beforeEach(() => {
logs.splice(0);
fetchZone = Zone.current.fork({
name: 'fetch',
onScheduleTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, task: Task) => {
if (task.type !== 'eventTask') {
logs.push(`scheduleTask:${task.source}:${task.type}`);
}
if (task.source === 'fetch') {
fetchTask = task;
}
return delegate.scheduleTask(target, task);
},
onInvokeTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, task: Task,
applyThis: any, applyArgs: any) => {
if (task.type !== 'eventTask') {
logs.push(`invokeTask:${task.source}:${task.type}`);
}
return delegate.invokeTask(target, task, applyThis, applyArgs);
},
onCancelTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, task: Task) => {
if (task.type !== 'eventTask') {
logs.push(`cancelTask:${task.source}:${task.type}`);
}
return delegate.cancelTask(target, task);
}
});
});
it('fetch should be considered as macroTask', (done: DoneFn) => {
fetchZone.run(() => {
global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json')
.then(function(response: any) {
expect(Zone.current.name).toBe(fetchZone.name);
expect(logs).toEqual([
'scheduleTask:fetch:macroTask', 'scheduleTask:Promise.then:microTask',
'invokeTask:Promise.then:microTask', 'invokeTask:fetch:macroTask',
'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask'
]);
done();
});
});
});
it('cancel fetch should invoke onCancelTask',
ifEnvSupportsWithDone('AbortController', (done: DoneFn) => {
if (isSafari) {
// safari not work with AbortController
done();
return;
}
fetchZone.run(() => {
const AbortController = global['AbortController'];
const abort = new AbortController();
const signal = abort.signal;
global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json', {signal})
.then(function(response: any) { fail('should not get response'); })
.catch(function(error: any) {
expect(error.name).toEqual('AbortError');
expect(logs).toEqual([
'scheduleTask:fetch:macroTask', 'cancelTask:fetch:macroTask',
'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask',
'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask',
'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask'
]);
done();
});
abort.abort();
});
}));
it('cancel fetchTask should trigger abort',
ifEnvSupportsWithDone('AbortController', (done: DoneFn) => {
if (isSafari) {
// safari not work with AbortController
done();
return;
}
fetchZone.run(() => {
const AbortController = global['AbortController'];
const abort = new AbortController();
const signal = abort.signal;
global['fetch']('/base/angular/packages/zone.js/test/assets/sample.json', {signal})
.then(function(response: any) { fail('should not get response'); })
.catch(function(error: any) {
expect(error.name).toEqual('AbortError');
expect(logs).toEqual([
'scheduleTask:fetch:macroTask', 'cancelTask:fetch:macroTask',
'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask',
'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask',
'scheduleTask:Promise.then:microTask', 'invokeTask:Promise.then:microTask'
]);
done();
});
fetchTask.zone.cancelTask(fetchTask);
});
}));
});
}));

View File

@ -0,0 +1,100 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
describe('Microtasks', function() {
if (!global.Promise) return;
function scheduleFn(task: Task) { Promise.resolve().then(<any>task.invoke); }
it('should execute microtasks enqueued in the root zone', function(done) {
const log: number[] = [];
Zone.current.scheduleMicroTask('test', () => log.push(1), undefined, scheduleFn);
Zone.current.scheduleMicroTask('test', () => log.push(2), undefined, scheduleFn);
Zone.current.scheduleMicroTask('test', () => log.push(3), undefined, scheduleFn);
setTimeout(function() {
expect(log).toEqual([1, 2, 3]);
done();
}, 10);
});
it('should correctly scheduleMacroTask microtasks vs macrotasks', function(done) {
const log = ['+root'];
Zone.current.scheduleMicroTask('test', () => log.push('root.mit'), undefined, scheduleFn);
setTimeout(function() {
log.push('+mat1');
Zone.current.scheduleMicroTask('test', () => log.push('mat1.mit'), undefined, scheduleFn);
log.push('-mat1');
}, 10);
setTimeout(function() { log.push('mat2'); }, 30);
setTimeout(function() {
expect(log).toEqual(['+root', '-root', 'root.mit', '+mat1', '-mat1', 'mat1.mit', 'mat2']);
done();
}, 40);
log.push('-root');
});
it('should execute Promise wrapCallback in the zone where they are scheduled', function(done) {
const resolvedPromise = Promise.resolve(null);
const testZone = Zone.current.fork({name: ''});
testZone.run(function() {
resolvedPromise.then(function() {
expect(Zone.current.name).toBe(testZone.name);
done();
});
});
});
it('should execute Promise wrapCallback in the zone where they are scheduled even if resolved ' +
'in different zone.',
function(done) {
let resolve: Function;
const promise = new Promise(function(rs) { resolve = rs; });
const testZone = Zone.current.fork({name: 'test'});
testZone.run(function() {
promise.then(function() {
expect(Zone.current).toBe(testZone);
done();
});
});
Zone.current.fork({name: 'test'}).run(function() { resolve(null); });
});
describe('Promise', function() {
it('should go through scheduleTask', function(done) {
let called = false;
const testZone = Zone.current.fork({
name: 'test',
onScheduleTask: function(delegate: ZoneDelegate, current: Zone, target: Zone, task: Task):
Task {
called = true;
delegate.scheduleTask(target, task);
return task;
}
});
testZone.run(function() {
Promise.resolve('value').then(function() {
expect(called).toEqual(true);
done();
});
});
});
});
});

View File

@ -0,0 +1,89 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
'use strict';
import {isNode, zoneSymbol} from '../../lib/common/utils';
declare const global: any;
const wtfMock = global.wtfMock;
describe('setInterval', function() {
it('should work with setInterval', function(done) {
let cancelId: any;
const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'});
testZone.run(() => {
let intervalCount = 0;
let timeoutRunning = false;
const intervalFn = function() {
intervalCount++;
expect(Zone.current.name).toEqual(('TestZone'));
if (timeoutRunning) {
return;
}
timeoutRunning = true;
global[zoneSymbol('setTimeout')](function() {
const intervalUnitLog = [
'> Zone:invokeTask:setInterval("<root>::ProxyZone::WTF::TestZone")',
'< Zone:invokeTask:setInterval'
];
let intervalLog: string[] = [];
for (let i = 0; i < intervalCount; i++) {
intervalLog = intervalLog.concat(intervalUnitLog);
}
expect(wtfMock.log[0]).toEqual('# Zone:fork("<root>::ProxyZone::WTF", "TestZone")');
expect(wtfMock.log[1])
.toEqual('> Zone:invoke:unit-test("<root>::ProxyZone::WTF::TestZone")');
expect(wtfMock.log[2])
.toContain(
'# Zone:schedule:macroTask:setInterval("<root>::ProxyZone::WTF::TestZone"');
expect(wtfMock.log[3]).toEqual('< Zone:invoke:unit-test');
expect(wtfMock.log.splice(4)).toEqual(intervalLog);
clearInterval(cancelId);
done();
});
};
expect(Zone.current.name).toEqual(('TestZone'));
cancelId = setInterval(intervalFn, 10);
if (isNode) {
expect(typeof cancelId.ref).toEqual(('function'));
expect(typeof cancelId.unref).toEqual(('function'));
}
expect(wtfMock.log[0]).toEqual('# Zone:fork("<root>::ProxyZone::WTF", "TestZone")');
expect(wtfMock.log[1]).toEqual('> Zone:invoke:unit-test("<root>::ProxyZone::WTF::TestZone")');
expect(wtfMock.log[2])
.toContain('# Zone:schedule:macroTask:setInterval("<root>::ProxyZone::WTF::TestZone"');
}, null, undefined, 'unit-test');
});
it('should not cancel the task after invoke the setInterval callback', (done) => {
const logs: HasTaskState[] = [];
const zone = Zone.current.fork({
name: 'interval',
onHasTask:
(delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, hasTask: HasTaskState) => {
logs.push(hasTask);
return delegate.hasTask(targetZone, hasTask);
}
});
zone.run(() => {
const timerId = setInterval(() => {}, 100);
(global as any)[Zone.__symbol__('setTimeout')](() => {
expect(logs.length > 0).toBeTruthy();
expect(logs).toEqual(
[{microTask: false, macroTask: true, eventTask: false, change: 'macroTask'}]);
clearInterval(timerId);
expect(logs).toEqual([
{microTask: false, macroTask: true, eventTask: false, change: 'macroTask'},
{microTask: false, macroTask: false, eventTask: false, change: 'macroTask'}
]);
done();
}, 300);
});
});
});

View File

@ -0,0 +1,123 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {isNode, zoneSymbol} from '../../lib/common/utils';
declare const global: any;
const wtfMock = global.wtfMock;
describe('setTimeout', function() {
it('should intercept setTimeout', function(done) {
let cancelId: any;
const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'});
testZone.run(() => {
const timeoutFn = function() {
expect(Zone.current.name).toEqual(('TestZone'));
global[zoneSymbol('setTimeout')](function() {
expect(wtfMock.log[0]).toEqual('# Zone:fork("<root>::ProxyZone::WTF", "TestZone")');
expect(wtfMock.log[1])
.toEqual('> Zone:invoke:unit-test("<root>::ProxyZone::WTF::TestZone")');
expect(wtfMock.log[2])
.toContain('# Zone:schedule:macroTask:setTimeout("<root>::ProxyZone::WTF::TestZone"');
expect(wtfMock.log[3]).toEqual('< Zone:invoke:unit-test');
expect(wtfMock.log[4])
.toEqual('> Zone:invokeTask:setTimeout("<root>::ProxyZone::WTF::TestZone")');
expect(wtfMock.log[5]).toEqual('< Zone:invokeTask:setTimeout');
done();
});
};
expect(Zone.current.name).toEqual(('TestZone'));
cancelId = setTimeout(timeoutFn, 3);
if (isNode) {
expect(typeof cancelId.ref).toEqual(('function'));
expect(typeof cancelId.unref).toEqual(('function'));
}
expect(wtfMock.log[0]).toEqual('# Zone:fork("<root>::ProxyZone::WTF", "TestZone")');
expect(wtfMock.log[1]).toEqual('> Zone:invoke:unit-test("<root>::ProxyZone::WTF::TestZone")');
expect(wtfMock.log[2])
.toContain('# Zone:schedule:macroTask:setTimeout("<root>::ProxyZone::WTF::TestZone"');
}, null, undefined, 'unit-test');
});
it('should allow canceling of fns registered with setTimeout', function(done) {
const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'});
testZone.run(() => {
const spy = jasmine.createSpy('spy');
const cancelId = setTimeout(spy, 0);
clearTimeout(cancelId);
setTimeout(function() {
expect(spy).not.toHaveBeenCalled();
done();
}, 1);
});
});
it('should allow cancelation of fns registered with setTimeout after invocation', function(done) {
const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'});
testZone.run(() => {
const spy = jasmine.createSpy('spy');
const cancelId = setTimeout(spy, 0);
setTimeout(function() {
expect(spy).toHaveBeenCalled();
setTimeout(function() {
clearTimeout(cancelId);
done();
});
}, 1);
});
});
it('should allow cancelation of fns while the task is being executed', function(done) {
const spy = jasmine.createSpy('spy');
const cancelId = setTimeout(() => {
clearTimeout(cancelId);
done();
}, 0);
});
it('should allow cancelation of fns registered with setTimeout during invocation',
function(done) {
const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'});
testZone.run(() => {
const cancelId = setTimeout(function() {
clearTimeout(cancelId);
done();
}, 0);
});
});
it('should return the original timeout Id', function() {
// Node returns complex object from setTimeout, ignore this test.
if (isNode) return;
const cancelId = setTimeout(() => {}, 0);
expect(typeof cancelId).toEqual('number');
});
it('should allow cancelation by numeric timeout Id', function(done) {
// Node returns complex object from setTimeout, ignore this test.
if (isNode) {
done();
return;
}
const testZone = Zone.current.fork((Zone as any)['wtfZoneSpec']).fork({name: 'TestZone'});
testZone.run(() => {
const spy = jasmine.createSpy('spy');
const cancelId = setTimeout(spy, 0);
clearTimeout(cancelId);
setTimeout(function() {
expect(spy).not.toHaveBeenCalled();
done();
}, 1);
});
});
it('should pass invalid values through', function() {
clearTimeout(null as any);
clearTimeout(<any>{});
});
});

View File

@ -0,0 +1,965 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
const noop = function() {};
let log: {zone: string, taskZone: undefined | string, toState: TaskState, fromState: TaskState}[] =
[];
const detectTask = Zone.current.scheduleMacroTask('detectTask', noop, undefined, noop, noop);
const originalTransitionTo = detectTask.constructor.prototype._transitionTo;
// patch _transitionTo of ZoneTask to add log for test
const logTransitionTo: Function = function(
toState: TaskState, fromState1: TaskState, fromState2?: TaskState) {
log.push({
zone: Zone.current.name,
taskZone: this.zone && this.zone.name,
toState: toState,
fromState: this._state
});
originalTransitionTo.apply(this, arguments);
};
function testFnWithLoggedTransitionTo(testFn: Function) {
return function() {
detectTask.constructor.prototype._transitionTo = logTransitionTo;
testFn.apply(this, arguments);
detectTask.constructor.prototype._transitionTo = originalTransitionTo;
};
}
describe('task lifecycle', () => {
describe('event task lifecycle', () => {
beforeEach(() => { log = []; });
it('task should transit from notScheduled to scheduling then to scheduled state when scheduleTask',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testEventTaskZone'}).run(() => {
Zone.current.scheduleEventTask('testEventTask', noop, undefined, noop, noop);
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'}
]);
}));
it('task should transit from scheduling to unknown when zoneSpec onScheduleTask callback throw error',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testEventTaskZone',
onScheduleTask: (delegate, currZone, targetZone, task) => {
throw Error('error in onScheduleTask');
}
})
.run(() => {
try {
Zone.current.scheduleEventTask('testEventTask', noop, undefined, noop, noop);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'unknown', fromState: 'scheduling'}
]);
}));
it('task should transit from scheduled to running when task is invoked then from running to scheduled after invoke',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testEventTaskZone'}).run(() => {
const task =
Zone.current.scheduleEventTask('testEventTask', noop, undefined, noop, noop);
task.invoke();
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'scheduled', fromState: 'running'}
]);
}));
it('task should transit from scheduled to canceling then from canceling to notScheduled when task is canceled before running',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testEventTaskZone'}).run(() => {
const task =
Zone.current.scheduleEventTask('testEventTask', noop, undefined, noop, noop);
Zone.current.cancelTask(task);
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
it('task should transit from running to canceling then from canceling to notScheduled when task is canceled in running state',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testEventTaskZone'}).run(() => {
const task = Zone.current.scheduleEventTask(
'testEventTask', () => { Zone.current.cancelTask(task); }, undefined, noop, noop);
task.invoke();
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'canceling', fromState: 'running'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
it('task should transit from running to scheduled when task.callback throw error',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testEventTaskZone'}).run(() => {
const task = Zone.current.scheduleEventTask(
'testEventTask', () => { throw Error('invoke error'); }, undefined, noop, noop);
try {
task.invoke();
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'scheduled', fromState: 'running'}
]);
}));
it('task should transit from canceling to unknown when zoneSpec.onCancelTask throw error before task running',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testEventTaskZone'}).run(() => {
const task = Zone.current.scheduleEventTask(
'testEventTask', noop, undefined, noop, () => { throw Error('cancel task'); });
try {
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'unknown', fromState: 'canceling'}
]);
}));
it('task should transit from canceling to unknown when zoneSpec.onCancelTask throw error in running state',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testEventTaskZone'}).run(() => {
const task = Zone.current.scheduleEventTask(
'testEventTask', noop, undefined, noop, () => { throw Error('cancel task'); });
try {
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'unknown', fromState: 'canceling'}
]);
}));
it('task should transit from notScheduled to scheduled if zoneSpec.onHasTask throw error when scheduleTask',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testEventTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
throw Error('hasTask Error');
}
})
.run(() => {
try {
Zone.current.scheduleEventTask('testEventTask', noop, undefined, noop, noop);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'}
]);
}));
it('task should transit to notScheduled state if zoneSpec.onHasTask throw error when task is canceled',
testFnWithLoggedTransitionTo(() => {
let task: Task;
Zone.current
.fork({
name: 'testEventTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
if (task && task.state === 'canceling') {
throw Error('hasTask Error');
}
}
})
.run(() => {
try {
task =
Zone.current.scheduleEventTask('testEventTask', noop, undefined, noop, noop);
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
});
describe('non periodical macroTask lifecycle', () => {
beforeEach(() => { log = []; });
it('task should transit from notScheduled to scheduling then to scheduled state when scheduleTask',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMacroTaskZone'}).run(() => {
Zone.current.scheduleMacroTask('testMacroTask', noop, undefined, noop, noop);
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'}
]);
}));
it('task should transit from scheduling to unknown when zoneSpec onScheduleTask callback throw error',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testMacroTaskZone',
onScheduleTask: (delegate, currZone, targetZone, task) => {
throw Error('error in onScheduleTask');
}
})
.run(() => {
try {
Zone.current.scheduleMacroTask('testMacroTask', noop, undefined, noop, noop);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'unknown', fromState: 'scheduling'}
]);
}));
it('task should transit from scheduled to running when task is invoked then from running to noScheduled after invoke',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMacroTaskZone'}).run(() => {
const task =
Zone.current.scheduleMacroTask('testMacroTask', noop, undefined, noop, noop);
task.invoke();
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'running'}
]);
}));
it('task should transit from scheduled to canceling then from canceling to notScheduled when task is canceled before running',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMacroTaskZone'}).run(() => {
const task =
Zone.current.scheduleMacroTask('testMacrotask', noop, undefined, noop, noop);
Zone.current.cancelTask(task);
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
it('task should transit from running to canceling then from canceling to notScheduled when task is canceled in running state',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMacroTaskZone'}).run(() => {
const task = Zone.current.scheduleMacroTask(
'testMacroTask', () => { Zone.current.cancelTask(task); }, undefined, noop, noop);
task.invoke();
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'canceling', fromState: 'running'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
it('task should transit from running to noScheduled when task.callback throw error',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMacroTaskZone'}).run(() => {
const task = Zone.current.scheduleMacroTask(
'testMacroTask', () => { throw Error('invoke error'); }, undefined, noop, noop);
try {
task.invoke();
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'running'}
]);
}));
it('task should transit from canceling to unknown when zoneSpec.onCancelTask throw error before task running',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMacroTaskZone'}).run(() => {
const task = Zone.current.scheduleMacroTask(
'testMacroTask', noop, undefined, noop, () => { throw Error('cancel task'); });
try {
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'unknown', fromState: 'canceling'}
]);
}));
it('task should transit from canceling to unknown when zoneSpec.onCancelTask throw error in running state',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMacroTaskZone'}).run(() => {
const task = Zone.current.scheduleMacroTask(
'testMacroTask', noop, undefined, noop, () => { throw Error('cancel task'); });
try {
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'unknown', fromState: 'canceling'}
]);
}));
it('task should transit from notScheduled to scheduling then to scheduled if zoneSpec.onHasTask throw error when scheduleTask',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testMacroTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
throw Error('hasTask Error');
}
})
.run(() => {
try {
Zone.current.scheduleMacroTask('testMacroTask', noop, undefined, noop, noop);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'}
]);
}));
it('task should transit to notScheduled state if zoneSpec.onHasTask throw error after task.callback being invoked',
testFnWithLoggedTransitionTo(() => {
let task: Task;
Zone.current
.fork({
name: 'testMacroTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
if (task && task.state === 'running') {
throw Error('hasTask Error');
}
}
})
.run(() => {
try {
task =
Zone.current.scheduleMacroTask('testMacroTask', noop, undefined, noop, noop);
task.invoke();
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'running'}
]);
}));
it('task should transit to notScheduled state if zoneSpec.onHasTask throw error when task is canceled before running',
testFnWithLoggedTransitionTo(() => {
let task: Task;
Zone.current
.fork({
name: 'testMacroTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
if (task && task.state === 'canceling') {
throw Error('hasTask Error');
}
}
})
.run(() => {
try {
task =
Zone.current.scheduleMacroTask('testMacroTask', noop, undefined, noop, noop);
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
});
describe('periodical macroTask lifecycle', () => {
let task: Task|null;
beforeEach(() => {
log = [];
task = null;
});
afterEach(() => {
task && task.state !== 'notScheduled' && task.state !== 'canceling' &&
task.state !== 'unknown' && task.zone.cancelTask(task);
});
it('task should transit from notScheduled to scheduling then to scheduled state when scheduleTask',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testPeriodicalTaskZone'}).run(() => {
task = Zone.current.scheduleMacroTask(
'testPeriodicalTask', noop, {isPeriodic: true}, noop, noop);
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'}
]);
}));
it('task should transit from scheduling to unknown when zoneSpec onScheduleTask callback throw error',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testPeriodicalTaskZone',
onScheduleTask: (delegate, currZone, targetZone, task) => {
throw Error('error in onScheduleTask');
}
})
.run(() => {
try {
task = Zone.current.scheduleMacroTask(
'testPeriodicalTask', noop, {isPeriodic: true}, noop, noop);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'unknown', fromState: 'scheduling'}
]);
}));
it('task should transit from scheduled to running when task is invoked then from running to scheduled after invoke',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testPeriodicalTaskZone'}).run(() => {
task = Zone.current.scheduleMacroTask(
'testPeriodicalTask', noop, {isPeriodic: true}, noop, noop);
task.invoke();
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'scheduled', fromState: 'running'}
]);
}));
it('task should transit from scheduled to canceling then from canceling to notScheduled when task is canceled before running',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testPeriodicalTaskZone'}).run(() => {
task = Zone.current.scheduleMacroTask(
'testPeriodicalTask', noop, {isPeriodic: true}, noop, noop);
Zone.current.cancelTask(task);
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
it('task should transit from running to canceling then from canceling to notScheduled when task is canceled in running state',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testPeriodicalTaskZone'}).run(() => {
task = Zone.current.scheduleMacroTask('testPeriodicalTask', () => {
Zone.current.cancelTask(task !);
}, {isPeriodic: true}, noop, noop);
task.invoke();
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'canceling', fromState: 'running'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
it('task should transit from running to scheduled when task.callback throw error',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testPeriodicalTaskZone'}).run(() => {
task = Zone.current.scheduleMacroTask('testPeriodicalTask', () => {
throw Error('invoke error');
}, {isPeriodic: true}, noop, noop);
try {
task.invoke();
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'scheduled', fromState: 'running'}
]);
}));
it('task should transit from canceling to unknown when zoneSpec.onCancelTask throw error before task running',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testPeriodicalTaskZone'}).run(() => {
task = Zone.current.scheduleMacroTask(
'testPeriodicalTask', noop, {isPeriodic: true}, noop,
() => { throw Error('cancel task'); });
try {
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'unknown', fromState: 'canceling'}
]);
}));
it('task should transit from canceling to unknown when zoneSpec.onCancelTask throw error in running state',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testPeriodicalTaskZone'}).run(() => {
task = Zone.current.scheduleMacroTask(
'testPeriodicalTask', noop, {isPeriodic: true}, noop,
() => { throw Error('cancel task'); });
try {
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'unknown', fromState: 'canceling'}
]);
}));
it('task should transit from notScheduled to scheduled if zoneSpec.onHasTask throw error when scheduleTask',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testPeriodicalTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
throw Error('hasTask Error');
}
})
.run(() => {
try {
task = Zone.current.scheduleMacroTask(
'testPeriodicalTask', noop, {isPeriodic: true}, noop, noop);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'}
]);
}));
it('task should transit to notScheduled state if zoneSpec.onHasTask throw error when task is canceled',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testPeriodicalTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
if (task && task.state === 'canceling') {
throw Error('hasTask Error');
}
}
})
.run(() => {
try {
task = Zone.current.scheduleMacroTask(
'testPeriodicalTask', noop, {isPeriodic: true}, noop, noop);
Zone.current.cancelTask(task);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
});
describe('microTask lifecycle', () => {
beforeEach(() => { log = []; });
it('task should transit from notScheduled to scheduling then to scheduled state when scheduleTask',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMicroTaskZone'}).run(() => {
Zone.current.scheduleMicroTask('testMicroTask', noop, undefined, noop);
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'}
]);
}));
it('task should transit from scheduling to unknown when zoneSpec onScheduleTask callback throw error',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testMicroTaskZone',
onScheduleTask: (delegate, currZone, targetZone, task) => {
throw Error('error in onScheduleTask');
}
})
.run(() => {
try {
Zone.current.scheduleMicroTask('testMicroTask', noop, undefined, noop);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'unknown', fromState: 'scheduling'}
]);
}));
it('task should transit from scheduled to running when task is invoked then from running to noScheduled after invoke',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMicroTaskZone'}).run(() => {
const task = Zone.current.scheduleMicroTask('testMicroTask', noop, undefined, noop);
task.invoke();
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'running'}
]);
}));
it('should throw error when try to cancel a microTask', testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMicroTaskZone'}).run(() => {
const task = Zone.current.scheduleMicroTask('testMicroTask', () => {}, undefined, noop);
expect(() => { Zone.current.cancelTask(task); }).toThrowError('Task is not cancelable');
});
}));
it('task should transit from running to notScheduled when task.callback throw error',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'testMicroTaskZone'}).run(() => {
const task = Zone.current.scheduleMicroTask(
'testMicroTask', () => { throw Error('invoke error'); }, undefined, noop);
try {
task.invoke();
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'running'}
]);
}));
it('task should transit from notScheduled to scheduling then to scheduled if zoneSpec.onHasTask throw error when scheduleTask',
testFnWithLoggedTransitionTo(() => {
Zone.current
.fork({
name: 'testMicroTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
throw Error('hasTask Error');
}
})
.run(() => {
try {
Zone.current.scheduleMicroTask('testMicroTask', noop, undefined, noop);
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'}
]);
}));
it('task should transit to notScheduled state if zoneSpec.onHasTask throw error after task.callback being invoked',
testFnWithLoggedTransitionTo(() => {
let task: Task;
Zone.current
.fork({
name: 'testMicroTaskZone',
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
if (task && task.state === 'running') {
throw Error('hasTask Error');
}
}
})
.run(() => {
try {
task = Zone.current.scheduleMicroTask('testMicroTask', noop, undefined, noop);
task.invoke();
} catch (err) {
}
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'running', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'running'}
]);
}));
it('task should not run if task transite to notScheduled state which was canceled',
testFnWithLoggedTransitionTo(() => {
let task: Task;
Zone.current.fork({name: 'testCancelZone'}).run(() => {
const task =
Zone.current.scheduleEventTask('testEventTask', noop, undefined, noop, noop);
Zone.current.cancelTask(task);
task.invoke();
});
expect(log.map(item => { return {toState: item.toState, fromState: item.fromState}; }))
.toEqual([
{toState: 'scheduling', fromState: 'notScheduled'},
{toState: 'scheduled', fromState: 'scheduling'},
{toState: 'canceling', fromState: 'scheduled'},
{toState: 'notScheduled', fromState: 'canceling'}
]);
}));
});
describe('reschedule zone', () => {
let callbackLogs: ({pos: string, method: string, zone: string, task: string} | HasTaskState)[];
const newZone = Zone.root.fork({
name: 'new',
onScheduleTask: (delegate, currZone, targetZone, task) => {
callbackLogs.push(
{pos: 'before', method: 'onScheduleTask', zone: currZone.name, task: task.zone.name});
return delegate.scheduleTask(targetZone, task);
},
onInvokeTask: (delegate, currZone, targetZone, task, applyThis, applyArgs) => {
callbackLogs.push(
{pos: 'before', method: 'onInvokeTask', zone: currZone.name, task: task.zone.name});
return delegate.invokeTask(targetZone, task, applyThis, applyArgs);
},
onCancelTask: (delegate, currZone, targetZone, task) => {
callbackLogs.push(
{pos: 'before', method: 'onCancelTask', zone: currZone.name, task: task.zone.name});
return delegate.cancelTask(targetZone, task);
},
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
(hasTaskState as any)['zone'] = targetZone.name;
callbackLogs.push(hasTaskState);
return delegate.hasTask(targetZone, hasTaskState);
}
});
const zone = Zone.root.fork({
name: 'original',
onScheduleTask: (delegate, currZone, targetZone, task) => {
callbackLogs.push(
{pos: 'before', method: 'onScheduleTask', zone: currZone.name, task: task.zone.name});
task.cancelScheduleRequest();
task = newZone.scheduleTask(task);
callbackLogs.push(
{pos: 'after', method: 'onScheduleTask', zone: currZone.name, task: task.zone.name});
return task;
},
onInvokeTask: (delegate, currZone, targetZone, task, applyThis, applyArgs) => {
callbackLogs.push(
{pos: 'before', method: 'onInvokeTask', zone: currZone.name, task: task.zone.name});
return delegate.invokeTask(targetZone, task, applyThis, applyArgs);
},
onCancelTask: (delegate, currZone, targetZone, task) => {
callbackLogs.push(
{pos: 'before', method: 'onCancelTask', zone: currZone.name, task: task.zone.name});
return delegate.cancelTask(targetZone, task);
},
onHasTask: (delegate, currZone, targetZone, hasTaskState) => {
(<any>hasTaskState)['zone'] = targetZone.name;
callbackLogs.push(hasTaskState);
return delegate.hasTask(targetZone, hasTaskState);
}
});
beforeEach(() => { callbackLogs = []; });
it('should be able to reschedule zone when in scheduling state, after that, task will completely go to new zone, has nothing to do with original one',
testFnWithLoggedTransitionTo(() => {
zone.run(() => {
const t = Zone.current.scheduleMacroTask(
'testRescheduleZoneTask', noop, undefined, noop, noop);
t.invoke();
});
expect(callbackLogs).toEqual([
{pos: 'before', method: 'onScheduleTask', zone: 'original', task: 'original'},
{pos: 'before', method: 'onScheduleTask', zone: 'new', task: 'new'},
{microTask: false, macroTask: true, eventTask: false, change: 'macroTask', zone: 'new'},
{pos: 'after', method: 'onScheduleTask', zone: 'original', task: 'new'},
{pos: 'before', method: 'onInvokeTask', zone: 'new', task: 'new'}, {
microTask: false,
macroTask: false,
eventTask: false,
change: 'macroTask',
zone: 'new'
}
]);
}));
it('should not be able to reschedule task in notScheduled / running / canceling state',
testFnWithLoggedTransitionTo(() => {
Zone.current.fork({name: 'rescheduleNotScheduled'}).run(() => {
const t = Zone.current.scheduleMacroTask(
'testRescheduleZoneTask', noop, undefined, noop, noop);
Zone.current.cancelTask(t);
expect(() => { t.cancelScheduleRequest(); })
.toThrow(Error(
`macroTask 'testRescheduleZoneTask': can not transition to ` +
`'notScheduled', expecting state 'scheduling', was 'notScheduled'.`));
});
Zone.current
.fork({
name: 'rescheduleRunning',
onInvokeTask: (delegate, currZone, targetZone, task, applyThis, applyArgs) => {
expect(() => { task.cancelScheduleRequest(); })
.toThrow(Error(
`macroTask 'testRescheduleZoneTask': can not transition to ` +
`'notScheduled', expecting state 'scheduling', was 'running'.`));
}
})
.run(() => {
const t = Zone.current.scheduleMacroTask(
'testRescheduleZoneTask', noop, undefined, noop, noop);
t.invoke();
});
Zone.current
.fork({
name: 'rescheduleCanceling',
onCancelTask: (delegate, currZone, targetZone, task) => {
expect(() => { task.cancelScheduleRequest(); })
.toThrow(Error(
`macroTask 'testRescheduleZoneTask': can not transition to ` +
`'notScheduled', expecting state 'scheduling', was 'canceling'.`));
}
})
.run(() => {
const t = Zone.current.scheduleMacroTask(
'testRescheduleZoneTask', noop, undefined, noop, noop);
Zone.current.cancelTask(t);
});
}));
it('can not reschedule a task to a zone which is the descendants of the original zone',
testFnWithLoggedTransitionTo(() => {
const originalZone = Zone.root.fork({
name: 'originalZone',
onScheduleTask: (delegate, currZone, targetZone, task) => {
callbackLogs.push({
pos: 'before',
method: 'onScheduleTask',
zone: currZone.name,
task: task.zone.name
});
task.cancelScheduleRequest();
task = rescheduleZone.scheduleTask(task);
callbackLogs.push({
pos: 'after',
method: 'onScheduleTask',
zone: currZone.name,
task: task.zone.name
});
return task;
}
});
const rescheduleZone = originalZone.fork({name: 'rescheduleZone'});
expect(() => {
originalZone.run(() => {
Zone.current.scheduleMacroTask('testRescheduleZoneTask', noop, undefined, noop, noop);
});
})
.toThrowError(
'can not reschedule task to rescheduleZone which is descendants of the original zone originalZone');
}));
});
});

View File

@ -0,0 +1,88 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {zoneSymbol} from '../../lib/common/utils';
import {ifEnvSupports} from '../test-util';
const g: any =
typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global;
describe('global function patch', () => {
describe('isOriginal', () => {
it('setTimeout toString should be the same with non patched setTimeout', () => {
expect(Function.prototype.toString.call(setTimeout))
.toEqual(Function.prototype.toString.call(g[zoneSymbol('setTimeout')]));
});
it('MutationObserver toString should be the same with native version',
ifEnvSupports('MutationObserver', () => {
const nativeMutationObserver = g[zoneSymbol('MutationObserver')];
if (typeof nativeMutationObserver === 'function') {
expect(Function.prototype.toString.call(g['MutationObserver']))
.toEqual(Function.prototype.toString.call(nativeMutationObserver));
} else {
expect(Function.prototype.toString.call(g['MutationObserver']))
.toEqual(Object.prototype.toString.call(nativeMutationObserver));
}
}));
});
describe('isNative', () => {
it('ZoneAwareError toString should look like native',
() => { expect(Function.prototype.toString.call(Error)).toContain('[native code]'); });
it('Function toString should look like native', () => {
expect(Function.prototype.toString.call(Function.prototype.toString))
.toContain('[native code]');
});
it('EventTarget addEventListener should look like native', ifEnvSupports('HTMLElement', () => {
expect(Function.prototype.toString.call(HTMLElement.prototype.addEventListener))
.toContain('[native code]');
}));
});
});
describe('ZoneTask', () => {
it('should return handleId.toString if handleId is available', () => {
let macroTask1: any = undefined;
let macroTask2: any = undefined;
let microTask: any = undefined;
const zone = Zone.current.fork({
name: 'timer',
onScheduleTask: (delegate: ZoneDelegate, curr: Zone, target: Zone, task: Task) => {
if (task.type === 'macroTask') {
if (!macroTask1) {
macroTask1 = task;
} else {
macroTask2 = task;
}
} else if (task.type === 'microTask') {
microTask = task;
}
return task;
}
});
zone.run(() => {
const id1 = setTimeout(() => {});
clearTimeout(id1);
const id2 = setTimeout(() => {});
clearTimeout(id2);
Promise.resolve().then(() => {});
const macroTask1Str = macroTask1.toString();
const macroTask2Str = macroTask2.toString();
expect(typeof macroTask1Str).toEqual('string');
expect(macroTask1Str).toEqual(id1.toString());
expect(typeof macroTask2Str).toEqual('string');
expect(macroTask2Str).toEqual(id2.toString());
if (macroTask1.data && typeof macroTask1.data.handleId === 'number') {
expect(macroTask1Str).not.toEqual(macroTask2Str);
}
expect(typeof microTask.toString()).toEqual('string');
});
});
});

View File

@ -0,0 +1,271 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {patchMethod, patchProperty, patchPrototype, zoneSymbol} from '../../lib/common/utils';
describe('utils', function() {
describe('patchMethod', () => {
it('should patch target where the method is defined', () => {
let args: any[]|undefined;
let self: any;
class Type {
method(..._args: any[]) {
args = _args;
self = this;
return 'OK';
}
}
const method = Type.prototype.method;
let delegateMethod: Function;
let delegateSymbol: string;
const instance = new Type();
expect(patchMethod(instance, 'method', (delegate: Function, symbol: string, name: string) => {
expect(name).toEqual('method');
delegateMethod = delegate;
delegateSymbol = symbol;
return function(self, args) { return delegate.apply(self, ['patch', args[0]]); };
})).toBe(delegateMethod !);
expect(instance.method('a0')).toEqual('OK');
expect(args).toEqual(['patch', 'a0']);
expect(self).toBe(instance);
expect(delegateMethod !).toBe(method);
expect(delegateSymbol !).toEqual(zoneSymbol('method'));
expect((Type.prototype as any)[delegateSymbol !]).toBe(method);
});
it('should not double patch', () => {
const Type = function() {};
const method = Type.prototype.method = function() {};
patchMethod(Type.prototype, 'method', (delegate) => {
return function(self, args: any[]) { return delegate.apply(self, ['patch', ...args]); };
});
const pMethod = Type.prototype.method;
expect(pMethod).not.toBe(method);
patchMethod(Type.prototype, 'method', (delegate) => {
return function(self, args) { return delegate.apply(self, ['patch', ...args]); };
});
expect(pMethod).toBe(Type.prototype.method);
});
it('should not patch property which is not configurable', () => {
const TestType = function() {};
const originalDefineProperty = (Object as any)[zoneSymbol('defineProperty')];
if (originalDefineProperty) {
originalDefineProperty(
TestType.prototype, 'nonConfigurableProperty',
{configurable: false, writable: true, value: 'test'});
} else {
Object.defineProperty(
TestType.prototype, 'nonConfigurableProperty',
{configurable: false, writable: true, value: 'test'});
}
patchProperty(TestType.prototype, 'nonConfigurableProperty');
const desc = Object.getOwnPropertyDescriptor(TestType.prototype, 'nonConfigurableProperty');
expect(desc !.writable).toBeTruthy();
expect(!desc !.get).toBeTruthy();
});
});
describe('patchPrototype', () => {
it('non configurable property desc should be patched', () => {
'use strict';
const TestFunction: any = function() {};
const log: string[] = [];
Object.defineProperties(TestFunction.prototype, {
'property1': {
value: function Property1(callback: Function) { Zone.root.run(callback); },
writable: true,
configurable: true,
enumerable: true
},
'property2': {
value: function Property2(callback: Function) { Zone.root.run(callback); },
writable: true,
configurable: false,
enumerable: true
}
});
const zone = Zone.current.fork({name: 'patch'});
zone.run(() => {
const instance = new TestFunction();
instance.property1(() => { log.push('property1' + Zone.current.name); });
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property1<root>', 'property2<root>']);
log.length = 0;
patchPrototype(TestFunction.prototype, ['property1', 'property2']);
zone.run(() => {
const instance = new TestFunction();
instance.property1(() => { log.push('property1' + Zone.current.name); });
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property1patch', 'property2patch']);
});
it('non writable property desc should not be patched', () => {
'use strict';
const TestFunction: any = function() {};
const log: string[] = [];
Object.defineProperties(TestFunction.prototype, {
'property1': {
value: function Property1(callback: Function) { Zone.root.run(callback); },
writable: true,
configurable: true,
enumerable: true
},
'property2': {
value: function Property2(callback: Function) { Zone.root.run(callback); },
writable: false,
configurable: true,
enumerable: true
}
});
const zone = Zone.current.fork({name: 'patch'});
zone.run(() => {
const instance = new TestFunction();
instance.property1(() => { log.push('property1' + Zone.current.name); });
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property1<root>', 'property2<root>']);
log.length = 0;
patchPrototype(TestFunction.prototype, ['property1', 'property2']);
zone.run(() => {
const instance = new TestFunction();
instance.property1(() => { log.push('property1' + Zone.current.name); });
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property1patch', 'property2<root>']);
});
it('readonly property desc should not be patched', () => {
'use strict';
const TestFunction: any = function() {};
const log: string[] = [];
Object.defineProperties(TestFunction.prototype, {
'property1': {
get: function() {
if (!this._property1) {
this._property1 = function Property2(callback: Function) { Zone.root.run(callback); };
}
return this._property1;
},
set: function(func: Function) { this._property1 = func; },
configurable: true,
enumerable: true
},
'property2': {
get: function() {
return function Property2(callback: Function) { Zone.root.run(callback); };
},
configurable: true,
enumerable: true
}
});
const zone = Zone.current.fork({name: 'patch'});
zone.run(() => {
const instance = new TestFunction();
instance.property1(() => { log.push('property1' + Zone.current.name); });
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property1<root>', 'property2<root>']);
log.length = 0;
patchPrototype(TestFunction.prototype, ['property1', 'property2']);
zone.run(() => {
const instance = new TestFunction();
instance.property1(() => { log.push('property1' + Zone.current.name); });
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property1patch', 'property2<root>']);
});
it('non writable method should not be patched', () => {
'use strict';
const TestFunction: any = function() {};
const log: string[] = [];
Object.defineProperties(TestFunction.prototype, {
'property2': {
value: function Property2(callback: Function) { Zone.root.run(callback); },
writable: false,
configurable: true,
enumerable: true
}
});
const zone = Zone.current.fork({name: 'patch'});
zone.run(() => {
const instance = new TestFunction();
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property2<root>']);
log.length = 0;
patchMethod(
TestFunction.prototype, 'property2',
function(delegate: Function, delegateName: string, name: string) {
return function(self: any, args: any) { log.push('patched property2'); };
});
zone.run(() => {
const instance = new TestFunction();
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property2<root>']);
});
it('readonly method should not be patched', () => {
'use strict';
const TestFunction: any = function() {};
const log: string[] = [];
Object.defineProperties(TestFunction.prototype, {
'property2': {
get: function() {
return function Property2(callback: Function) { Zone.root.run(callback); };
},
configurable: true,
enumerable: true
}
});
const zone = Zone.current.fork({name: 'patch'});
zone.run(() => {
const instance = new TestFunction();
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property2<root>']);
log.length = 0;
patchMethod(
TestFunction.prototype, 'property2',
function(delegate: Function, delegateName: string, name: string) {
return function(self: any, args: any) { log.push('patched property2'); };
});
zone.run(() => {
const instance = new TestFunction();
instance.property2(() => { log.push('property2' + Zone.current.name); });
});
expect(log).toEqual(['property2<root>']);
});
});
});

View File

@ -0,0 +1,388 @@
/**
* @license
* Copyright Google Inc. All Rights Reserved.
*
* Use of this source code is governed by an MIT-style license that can be
* found in the LICENSE file at https://angular.io/license
*/
import {zoneSymbol} from '../../lib/common/utils';
describe('Zone', function() {
const rootZone = Zone.current;
it('should have a name', function() { expect(Zone.current.name).toBeDefined(); });
describe('hooks', function() {
it('should throw if onError is not defined',
function() { expect(function() { Zone.current.run(throwError); }).toThrow(); });
it('should fire onError if a function run by a zone throws', function() {
const errorSpy = jasmine.createSpy('error');
const myZone = Zone.current.fork({name: 'spy', onHandleError: errorSpy});
expect(errorSpy).not.toHaveBeenCalled();
expect(function() { myZone.runGuarded(throwError); }).not.toThrow();
expect(errorSpy).toHaveBeenCalled();
});
it('should send correct currentZone in hook method when in nested zone', function() {
const zone = Zone.current;
const zoneA = zone.fork({
name: 'A',
onInvoke: function(
parentDelegate, currentZone, targetZone, callback, applyThis, applyArgs, source) {
expect(currentZone.name).toEqual('A');
return parentDelegate.invoke(targetZone, callback, applyThis, applyArgs, source);
}
});
const zoneB = zoneA.fork({
name: 'B',
onInvoke: function(
parentDelegate, currentZone, targetZone, callback, applyThis, applyArgs, source) {
expect(currentZone.name).toEqual('B');
return parentDelegate.invoke(targetZone, callback, applyThis, applyArgs, source);
}
});
const zoneC = zoneB.fork({name: 'C'});
zoneC.run(function() {});
});
});
it('should allow zones to be run from within another zone', function() {
const zone = Zone.current;
const zoneA = zone.fork({name: 'A'});
const zoneB = zone.fork({name: 'B'});
zoneA.run(function() {
zoneB.run(function() { expect(Zone.current).toBe(zoneB); });
expect(Zone.current).toBe(zoneA);
});
expect(Zone.current).toBe(zone);
});
describe('wrap', function() {
it('should throw if argument is not a function', function() {
expect(function() {
(<Function>Zone.current.wrap)(11);
}).toThrowError('Expecting function got: 11');
});
});
describe('run out side of current zone', function() {
it('should be able to get root zone', function() {
Zone.current.fork({name: 'testZone'}).run(function() {
expect(Zone.root.name).toEqual('<root>');
});
});
it('should be able to get run under rootZone', function() {
Zone.current.fork({name: 'testZone'}).run(function() {
Zone.root.run(() => { expect(Zone.current.name).toEqual('<root>'); });
});
});
it('should be able to get run outside of current zone', function() {
Zone.current.fork({name: 'testZone'}).run(function() {
Zone.root.fork({name: 'newTestZone'}).run(() => {
expect(Zone.current.name).toEqual('newTestZone');
expect(Zone.current.parent !.name).toEqual('<root>');
});
});
});
});
describe('get', function() {
it('should store properties', function() {
const testZone = Zone.current.fork({name: 'A', properties: {key: 'value'}});
expect(testZone.get('key')).toEqual('value');
expect(testZone.getZoneWith('key')).toEqual(testZone);
const childZone = testZone.fork({name: 'B', properties: {key: 'override'}});
expect(testZone.get('key')).toEqual('value');
expect(testZone.getZoneWith('key')).toEqual(testZone);
expect(childZone.get('key')).toEqual('override');
expect(childZone.getZoneWith('key')).toEqual(childZone);
});
});
describe('task', () => {
function noop() {}
let log: any[];
const zone: Zone = Zone.current.fork({
name: 'parent',
onHasTask: (delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState):
void => {
(hasTaskState as any)['zone'] = target.name;
log.push(hasTaskState);
},
onScheduleTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task) => {
// Do nothing to prevent tasks from being run on VM turn;
// Tests run task explicitly.
return task;
}
});
beforeEach(() => { log = []; });
it('task can only run in the zone of creation', () => {
const task =
zone.fork({name: 'createZone'}).scheduleMacroTask('test', noop, undefined, noop, noop);
expect(() => { Zone.current.fork({name: 'anotherZone'}).runTask(task); })
.toThrowError(
'A task can only be run in the zone of creation! (Creation: createZone; Execution: anotherZone)');
task.zone.cancelTask(task);
});
it('task can only cancel in the zone of creation', () => {
const task =
zone.fork({name: 'createZone'}).scheduleMacroTask('test', noop, undefined, noop, noop);
expect(() => { Zone.current.fork({name: 'anotherZone'}).cancelTask(task); })
.toThrowError(
'A task can only be cancelled in the zone of creation! (Creation: createZone; Execution: anotherZone)');
task.zone.cancelTask(task);
});
it('should prevent double cancellation', () => {
const task =
zone.scheduleMacroTask('test', () => log.push('macroTask'), undefined, noop, noop);
zone.cancelTask(task);
try {
zone.cancelTask(task);
} catch (e) {
expect(e.message).toContain(
'macroTask \'test\': can not transition to \'canceling\', expecting state \'scheduled\' or \'running\', was \'notScheduled\'.');
}
});
it('should not decrement counters on periodic tasks', () => {
zone.run(() => {
const task = zone.scheduleMacroTask(
'test', () => log.push('macroTask'), {isPeriodic: true}, noop, noop);
zone.runTask(task);
zone.runTask(task);
zone.cancelTask(task);
});
expect(log).toEqual([
{microTask: false, macroTask: true, eventTask: false, change: 'macroTask', zone: 'parent'},
'macroTask', 'macroTask', {
microTask: false,
macroTask: false,
eventTask: false,
change: 'macroTask',
zone: 'parent'
}
]);
});
it('should notify of queue status change', () => {
zone.run(() => {
const z = Zone.current;
z.runTask(z.scheduleMicroTask('test', () => log.push('microTask')));
z.cancelTask(
z.scheduleMacroTask('test', () => log.push('macroTask'), undefined, noop, noop));
z.cancelTask(
z.scheduleEventTask('test', () => log.push('eventTask'), undefined, noop, noop));
});
expect(log).toEqual([
{microTask: true, macroTask: false, eventTask: false, change: 'microTask', zone: 'parent'},
'microTask',
{microTask: false, macroTask: false, eventTask: false, change: 'microTask', zone: 'parent'},
{microTask: false, macroTask: true, eventTask: false, change: 'macroTask', zone: 'parent'},
{microTask: false, macroTask: false, eventTask: false, change: 'macroTask', zone: 'parent'},
{microTask: false, macroTask: false, eventTask: true, change: 'eventTask', zone: 'parent'},
{
microTask: false,
macroTask: false,
eventTask: false,
change: 'eventTask',
zone: 'parent'
}
]);
});
it('should notify of queue status change on parent task', () => {
zone.fork({name: 'child'}).run(() => {
const z = Zone.current;
z.runTask(z.scheduleMicroTask('test', () => log.push('microTask')));
});
expect(log).toEqual([
{microTask: true, macroTask: false, eventTask: false, change: 'microTask', zone: 'child'},
{microTask: true, macroTask: false, eventTask: false, change: 'microTask', zone: 'parent'},
'microTask',
{microTask: false, macroTask: false, eventTask: false, change: 'microTask', zone: 'child'},
{microTask: false, macroTask: false, eventTask: false, change: 'microTask', zone: 'parent'},
]);
});
it('should allow rescheduling a task on a separate zone', () => {
const log: any[] = [];
const zone = Zone.current.fork({
name: 'test-root',
onHasTask:
(delegate: ZoneDelegate, current: Zone, target: Zone, hasTaskState: HasTaskState) => {
(hasTaskState as any)['zone'] = target.name;
log.push(hasTaskState);
}
});
const left = zone.fork({name: 'left'});
const right = zone.fork({
name: 'right',
onScheduleTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): Task => {
log.push(
{pos: 'before', method: 'onScheduleTask', zone: current.name, task: task.zone.name});
// Cancel the current scheduling of the task
task.cancelScheduleRequest();
// reschedule on a different zone.
task = left.scheduleTask(task);
log.push(
{pos: 'after', method: 'onScheduleTask', zone: current.name, task: task.zone.name});
return task;
}
});
const rchild = right.fork({
name: 'rchild',
onScheduleTask: (delegate: ZoneDelegate, current: Zone, target: Zone, task: Task): Task => {
log.push(
{pos: 'before', method: 'onScheduleTask', zone: current.name, task: task.zone.name});
task = delegate.scheduleTask(target, task);
log.push(
{pos: 'after', method: 'onScheduleTask', zone: current.name, task: task.zone.name});
expect((task as any)._zoneDelegates.map((zd: ZoneDelegate) => zd.zone.name)).toEqual([
'left', 'test-root', 'ProxyZone'
]);
return task;
}
});
const task = rchild.scheduleMacroTask('testTask', () => log.push('WORK'), {}, noop, noop);
expect(task.zone).toEqual(left);
log.push(task.zone.name);
task.invoke();
expect(log).toEqual([
{pos: 'before', method: 'onScheduleTask', zone: 'rchild', task: 'rchild'},
{pos: 'before', method: 'onScheduleTask', zone: 'right', task: 'rchild'},
{microTask: false, macroTask: true, eventTask: false, change: 'macroTask', zone: 'left'}, {
microTask: false,
macroTask: true,
eventTask: false,
change: 'macroTask',
zone: 'test-root'
},
{pos: 'after', method: 'onScheduleTask', zone: 'right', task: 'left'},
{pos: 'after', method: 'onScheduleTask', zone: 'rchild', task: 'left'}, 'left', 'WORK',
{microTask: false, macroTask: false, eventTask: false, change: 'macroTask', zone: 'left'}, {
microTask: false,
macroTask: false,
eventTask: false,
change: 'macroTask',
zone: 'test-root'
}
]);
});
it('period task should not transit to scheduled state after being cancelled in running state',
() => {
const zone = Zone.current.fork({name: 'testZone'});
const task = zone.scheduleMacroTask('testPeriodTask', () => {
zone.cancelTask(task);
}, {isPeriodic: true}, () => {}, () => {});
task.invoke();
expect(task.state).toBe('notScheduled');
});
it('event task should not transit to scheduled state after being cancelled in running state',
() => {
const zone = Zone.current.fork({name: 'testZone'});
const task = zone.scheduleEventTask(
'testEventTask', () => { zone.cancelTask(task); }, undefined, () => {}, () => {});
task.invoke();
expect(task.state).toBe('notScheduled');
});
describe('assert ZoneAwarePromise', () => {
it('should not throw when all is OK', () => { Zone.assertZonePatched(); });
it('should keep ZoneAwarePromise has been patched', () => {
class WrongPromise {
static resolve(value: any) {}
then() {}
}
const ZoneAwarePromise = global.Promise;
const NativePromise = (global as any)[zoneSymbol('Promise')];
global.Promise = WrongPromise;
try {
expect(ZoneAwarePromise).toBeTruthy();
Zone.assertZonePatched();
expect(global.Promise).toBe(ZoneAwarePromise);
} finally {
// restore it.
global.Promise = NativePromise;
}
Zone.assertZonePatched();
});
});
});
describe('invoking tasks', () => {
let log: string[];
function noop() {}
beforeEach(() => { log = []; });
it('should not drain the microtask queue too early', () => {
const z = Zone.current;
const event = z.scheduleEventTask('test', () => log.push('eventTask'), undefined, noop, noop);
z.scheduleMicroTask('test', () => log.push('microTask'));
const macro = z.scheduleMacroTask('test', () => {
event.invoke();
// At this point, we should not have invoked the microtask.
expect(log).toEqual(['eventTask']);
}, undefined, noop, noop);
macro.invoke();
});
it('should convert task to json without cyclic error', () => {
const z = Zone.current;
const event = z.scheduleEventTask('test', () => {}, undefined, noop, noop);
const micro = z.scheduleMicroTask('test', () => {});
const macro = z.scheduleMacroTask('test', () => {}, undefined, noop, noop);
expect(function() { JSON.stringify(event); }).not.toThrow();
expect(function() { JSON.stringify(micro); }).not.toThrow();
expect(function() { JSON.stringify(macro); }).not.toThrow();
});
it('should call onHandleError callback when zoneSpec onHasTask throw error', () => {
const spy = jasmine.createSpy('error');
const hasTaskZone = Zone.current.fork({
name: 'hasTask',
onHasTask: (delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone,
hasTasState: HasTaskState) => { throw new Error('onHasTask Error'); },
onHandleError:
(delegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: Error) => {
spy(error.message);
return delegate.handleError(targetZone, error);
}
});
const microTask = hasTaskZone.scheduleMicroTask('test', () => {}, undefined, () => {});
expect(spy).toHaveBeenCalledWith('onHasTask Error');
});
});
});
function throwError() {
throw new Error();
}