license const noop = function ( ) { } ; let log = [ ] ; const detectTask = Zone . current . scheduleMacroTask ( 'detectTask' , noop , undefined , noop , noop ) ; const originalTransitionTo = detectTask . constructor . prototype . _transitionTo ; const logTransitionTo = function ( toState , fromState1 , fromState2 ? ) { log . push ( { zone : Zone . current . name , taskZone : this . zone && this . zone . name , toState : toState , fromState : this . _state } ) ; originalTransitionTo . apply ( this , arguments ) ; } ; function testFnWithLoggedTransitionTo ( testFn ) { 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 ; 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 ; 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 ; 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 ; 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 ; 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 ; 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 ; 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' ) ; } ) ) ; } ) ; } ) ;