diff --git a/lib/zone-spec/fake-async-test.ts b/lib/zone-spec/fake-async-test.ts index a22134002..5123743a5 100644 --- a/lib/zone-spec/fake-async-test.ts +++ b/lib/zone-spec/fake-async-test.ts @@ -23,6 +23,12 @@ target: any; } + interface MacroTaskOptions { + source: string; + isPeriodic?: boolean; + callbackArgs?: any; + } + class Scheduler { // Next scheduler id. public nextId: number = 0; @@ -172,8 +178,15 @@ pendingPeriodicTimers: number[] = []; pendingTimers: number[] = []; - constructor(namePrefix: string, private trackPendingRequestAnimationFrame = false) { + constructor( + namePrefix: string, private trackPendingRequestAnimationFrame = false, + private macroTaskOptions?: MacroTaskOptions[]) { this.name = 'fakeAsyncTestZone for ' + namePrefix; + // in case user can't access the construction of FakyAsyncTestSpec + // user can also define macroTaskOptions by define a global variable. + if (!this.macroTaskOptions) { + this.macroTaskOptions = global[Zone.__symbol__('FakeAsyncTestMacroTask')]; + } } private _fnAndFlush(fn: Function, completers: {onSuccess?: Function, onError?: Function}): @@ -352,6 +365,24 @@ this.trackPendingRequestAnimationFrame); break; default: + // user can define which macroTask they want to support by passing + // macroTaskOptions + const macroTaskOption = this.findMacroTaskOption(task); + if (macroTaskOption) { + const args = task.data && (task.data as any)['args']; + const delay = args && args.length > 1 ? args[1] : 0; + let callbackArgs = + macroTaskOption.callbackArgs ? macroTaskOption.callbackArgs : args; + if (!!macroTaskOption.isPeriodic) { + // periodic macroTask, use setInterval to simulate + task.data['handleId'] = this._setInterval(task.invoke, delay, callbackArgs); + task.data.isPeriodic = true; + } else { + // not periodic, use setTimout to simulate + task.data['handleId'] = this._setTimeout(task.invoke, delay, callbackArgs); + } + break; + } throw new Error('Unknown macroTask scheduled in fake async test: ' + task.source); } break; @@ -372,10 +403,31 @@ case 'setInterval': return this._clearInterval(task.data['handleId']); default: + // user can define which macroTask they want to support by passing + // macroTaskOptions + const macroTaskOption = this.findMacroTaskOption(task); + if (macroTaskOption) { + const handleId = task.data['handleId']; + return macroTaskOption.isPeriodic ? this._clearInterval(handleId) : + this._clearTimeout(handleId); + } return delegate.cancelTask(target, task); } } + findMacroTaskOption(task: Task) { + if (!this.macroTaskOptions) { + return null; + } + for (let i = 0; i < this.macroTaskOptions.length; i++) { + const macroTaskOption = this.macroTaskOptions[i]; + if (macroTaskOption.source === task.source) { + return macroTaskOption; + } + } + return null; + } + onHandleError( parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): boolean { diff --git a/test/test_fake_polyfill.ts b/test/test_fake_polyfill.ts index b6d3e63c3..27488c808 100644 --- a/test/test_fake_polyfill.ts +++ b/test/test_fake_polyfill.ts @@ -64,4 +64,5 @@ global['__Zone_ignore_on_properties'] = [{target: TestTarget.prototype, ignoreProperties: ['prop1']}]; + global['__zone_symbol__FakeAsyncTestMacroTask'] = [{source: 'TestClass.myTimeout'}]; })(typeof window === 'object' && window || typeof self === 'object' && self || global); diff --git a/test/zone-spec/fake-async-test.spec.ts b/test/zone-spec/fake-async-test.spec.ts index 13a96afa8..8c53da96b 100644 --- a/test/zone-spec/fake-async-test.spec.ts +++ b/test/zone-spec/fake-async-test.spec.ts @@ -8,7 +8,7 @@ import '../../lib/zone-spec/fake-async-test'; -import {isNode} from '../../lib/common/utils'; +import {isNode, patchMacroTask} from '../../lib/common/utils'; import {ifEnvSupports} from '../test-util'; function supportNode() { @@ -715,4 +715,94 @@ describe('FakeAsyncTestZoneSpec', () => { }); })); + + describe('should allow user define which macroTask fakeAsyncTest', () => { + let FakeAsyncTestZoneSpec = (Zone as any)['FakeAsyncTestZoneSpec']; + let testZoneSpec: any; + let fakeAsyncTestZone: Zone; + it('should support custom non perodic macroTask', () => { + testZoneSpec = new FakeAsyncTestZoneSpec( + 'name', false, [{source: 'TestClass.myTimeout', callbackArgs: ['test']}]); + class TestClass { + myTimeout(callback: Function) {} + } + fakeAsyncTestZone = Zone.current.fork(testZoneSpec); + fakeAsyncTestZone.run(() => { + let ran = false; + patchMacroTask( + TestClass.prototype, 'myTimeout', + (self: any, args: any[]) => + ({name: 'TestClass.myTimeout', target: self, callbackIndex: 0, args: args})); + + const testClass = new TestClass(); + testClass.myTimeout(function(callbackArgs: any) { + ran = true; + expect(callbackArgs).toEqual('test'); + }); + + expect(ran).toEqual(false); + + testZoneSpec.tick(); + expect(ran).toEqual(true); + }); + }); + + it('should support custom non perodic macroTask by global flag', () => { + testZoneSpec = new FakeAsyncTestZoneSpec('name'); + class TestClass { + myTimeout(callback: Function) {} + } + fakeAsyncTestZone = Zone.current.fork(testZoneSpec); + fakeAsyncTestZone.run(() => { + let ran = false; + patchMacroTask( + TestClass.prototype, 'myTimeout', + (self: any, args: any[]) => + ({name: 'TestClass.myTimeout', target: self, callbackIndex: 0, args: args})); + + const testClass = new TestClass(); + testClass.myTimeout(() => { + ran = true; + }); + + expect(ran).toEqual(false); + + testZoneSpec.tick(); + expect(ran).toEqual(true); + }); + }); + + + it('should support custom perodic macroTask', () => { + testZoneSpec = new FakeAsyncTestZoneSpec( + 'name', false, [{source: 'TestClass.myInterval', isPeriodic: true}]); + fakeAsyncTestZone = Zone.current.fork(testZoneSpec); + fakeAsyncTestZone.run(() => { + let cycle = 0; + class TestClass { + myInterval(callback: Function, interval: number): any { + return null; + } + } + patchMacroTask( + TestClass.prototype, 'myInterval', + (self: any, args: any[]) => + ({name: 'TestClass.myInterval', target: self, callbackIndex: 0, args: args})); + + const testClass = new TestClass(); + const id = testClass.myInterval(() => { + cycle++; + }, 10); + + expect(cycle).toEqual(0); + + testZoneSpec.tick(10); + expect(cycle).toEqual(1); + + testZoneSpec.tick(10); + expect(cycle).toEqual(2); + clearInterval(id); + }); + }); + }); });