
committed by
Kara Erickson

parent
7b3bcc23af
commit
5eb7426216
425
packages/zone.js/test/common/Error.spec.ts
Normal file
425
packages/zone.js/test/common/Error.spec.ts
Normal 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;
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
521
packages/zone.js/test/common/Promise.spec.ts
Normal file
521
packages/zone.js/test/common/Promise.spec.ts
Normal 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(); });
|
||||
});
|
||||
}));
|
205
packages/zone.js/test/common/fetch.spec.ts
Normal file
205
packages/zone.js/test/common/fetch.spec.ts
Normal 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);
|
||||
});
|
||||
}));
|
||||
});
|
||||
}));
|
100
packages/zone.js/test/common/microtasks.spec.ts
Normal file
100
packages/zone.js/test/common/microtasks.spec.ts
Normal 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();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
89
packages/zone.js/test/common/setInterval.spec.ts
Normal file
89
packages/zone.js/test/common/setInterval.spec.ts
Normal 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);
|
||||
});
|
||||
});
|
||||
});
|
123
packages/zone.js/test/common/setTimeout.spec.ts
Normal file
123
packages/zone.js/test/common/setTimeout.spec.ts
Normal 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>{});
|
||||
});
|
||||
});
|
965
packages/zone.js/test/common/task.spec.ts
Normal file
965
packages/zone.js/test/common/task.spec.ts
Normal 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');
|
||||
}));
|
||||
});
|
||||
});
|
88
packages/zone.js/test/common/toString.spec.ts
Normal file
88
packages/zone.js/test/common/toString.spec.ts
Normal 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');
|
||||
});
|
||||
});
|
||||
});
|
271
packages/zone.js/test/common/util.spec.ts
Normal file
271
packages/zone.js/test/common/util.spec.ts
Normal 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>']);
|
||||
});
|
||||
});
|
||||
});
|
388
packages/zone.js/test/common/zone.spec.ts
Normal file
388
packages/zone.js/test/common/zone.spec.ts
Normal 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();
|
||||
}
|
Reference in New Issue
Block a user