angular-cn/packages/zone.js/test/common/task.spec.ts

967 lines
42 KiB
TypeScript
Raw Normal View History

/**
* @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(
this: Task&{_state: TaskState}, 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(this: unknown) {
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');
}));
});
});