/** * @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) => { (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'); })); }); });