diff --git a/gulpfile.js b/gulpfile.js index e455b7c68..bc661c079 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -97,6 +97,14 @@ gulp.task('build/long-stack-trace-zone.min.js', function(cb) { return generateBrowserScript('./lib/zone-spec/long-stack-trace.ts', 'long-stack-trace-zone.min.js', true, cb); }); +gulp.task('build/proxy-zone.js', function(cb) { + return generateBrowserScript('./lib/zone-spec/proxy.ts', 'proxy-zone.js', false, cb); +}); + +gulp.task('build/proxy-zone.min.js', function(cb) { + return generateBrowserScript('./lib/zone-spec/proxy.ts', 'proxy-zone.min.js', true, cb); +}); + gulp.task('build/wtf.js', function(cb) { return generateBrowserScript('./lib/zone-spec/wtf.ts', 'wtf.js', false, cb); }); @@ -126,6 +134,8 @@ gulp.task('build', [ 'build/jasmine-patch.min.js', 'build/long-stack-trace-zone.js', 'build/long-stack-trace-zone.min.js', + 'build/proxy-zone.js', + 'build/proxy-zone.min.js', 'build/wtf.js', 'build/wtf.min.js', 'build/async-test.js', diff --git a/lib/zone-spec/proxy.ts b/lib/zone-spec/proxy.ts new file mode 100644 index 000000000..7c70741f3 --- /dev/null +++ b/lib/zone-spec/proxy.ts @@ -0,0 +1,128 @@ +(function () { + class ProxyZoneSpec implements ZoneSpec { + name: string = 'ProxyZone'; + + private _delegateSpec: ZoneSpec; + + properties: {[k: string]: any} = {'ProxyZoneSpec': this}; + propertyKeys: string[] = null; + + static get(): ProxyZoneSpec { + return Zone.current.get('ProxyZoneSpec'); + } + + static isLoaded(): boolean { + return ProxyZoneSpec.get() instanceof ProxyZoneSpec; + } + + static assertPresent(): ProxyZoneSpec { + if (!this.isLoaded()) { + throw new Error(`Expected to be running in 'ProxyZone', but it was not found.`); + } + return ProxyZoneSpec.get(); + } + + constructor(private defaultSpecDelegate: ZoneSpec = null) { + this.setDelegate(defaultSpecDelegate); + } + + + setDelegate(delegateSpec: ZoneSpec) { + this._delegateSpec = delegateSpec; + this.propertyKeys && this.propertyKeys.forEach((key) => delete this.properties[key]); + this.propertyKeys = null; + if (delegateSpec && delegateSpec.properties) { + this.propertyKeys = Object.keys(delegateSpec.properties); + this.propertyKeys.forEach((k) => this.properties[k] = delegateSpec.properties[k]); + } + } + + getDelegate() { + return this._delegateSpec; + } + + + resetDelegate() { + this.setDelegate(this.defaultSpecDelegate); + } + + + onFork(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + zoneSpec: ZoneSpec): Zone { + if (this._delegateSpec && this._delegateSpec.onFork) { + return this._delegateSpec.onFork(parentZoneDelegate, currentZone, targetZone, zoneSpec); + } else { + return parentZoneDelegate.fork(targetZone, zoneSpec); + } + } + + + onIntercept(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + delegate: Function, source: string): Function { + if (this._delegateSpec && this._delegateSpec.onIntercept) { + return this._delegateSpec.onIntercept(parentZoneDelegate, currentZone, targetZone, delegate, source); + } else { + return parentZoneDelegate.intercept(targetZone, delegate, source); + } + } + + + onInvoke(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + delegate: Function, applyThis: any, applyArgs: any[], source: string): any { + if (this._delegateSpec && this._delegateSpec.onInvoke) { + return this._delegateSpec.onInvoke(parentZoneDelegate, currentZone, targetZone, delegate, applyThis, applyArgs, source); + } else { + return parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source); + } + } + + onHandleError(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + error: any): boolean { + if (this._delegateSpec && this._delegateSpec.onHandleError) { + return this._delegateSpec.onHandleError(parentZoneDelegate, currentZone, targetZone, error); + } else { + return parentZoneDelegate.handleError(targetZone, error); + } + } + + onScheduleTask(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + task: Task): Task { + if (this._delegateSpec && this._delegateSpec.onScheduleTask) { + return this._delegateSpec.onScheduleTask(parentZoneDelegate, currentZone, targetZone, task); + } else { + return parentZoneDelegate.scheduleTask(targetZone, task); + } + } + + onInvokeTask(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + task: Task, applyThis: any, applyArgs: any): any { + if (this._delegateSpec && this._delegateSpec.onFork) { + return this._delegateSpec.onInvokeTask(parentZoneDelegate, currentZone, targetZone, task, applyThis, applyArgs); + } else { + return parentZoneDelegate.invokeTask(targetZone, task, applyThis, applyArgs); + } + } + + onCancelTask(parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + task: Task): any { + if (this._delegateSpec && this._delegateSpec.onCancelTask) { + return this._delegateSpec.onCancelTask(parentZoneDelegate, currentZone, targetZone, task); + } else { + return parentZoneDelegate.cancelTask(targetZone, task); + } + } + + onHasTask(delegate: ZoneDelegate, current: Zone, target: Zone, + hasTaskState: HasTaskState): void { + if (this._delegateSpec && this._delegateSpec.onHasTask) { + this._delegateSpec.onHasTask(delegate, current, target, hasTaskState); + } else { + delegate.hasTask(target, hasTaskState); + } + } + } + + // Export the class so that new instances can be created with proper + // constructor params. + Zone['ProxyZoneSpec'] = ProxyZoneSpec; +})(); diff --git a/test/browser_entry_point.ts b/test/browser_entry_point.ts index daccea2b4..8520e4f1f 100644 --- a/test/browser_entry_point.ts +++ b/test/browser_entry_point.ts @@ -6,6 +6,7 @@ import '../lib/zone'; import '../lib/browser/browser.ts'; import '../lib/zone-spec/long-stack-trace'; import '../lib/zone-spec/wtf'; +import '../lib/zone-spec/proxy'; // Setup test environment import './test-env-setup'; diff --git a/test/common_tests.ts b/test/common_tests.ts index f04327403..8bcfa8ce9 100644 --- a/test/common_tests.ts +++ b/test/common_tests.ts @@ -8,3 +8,4 @@ import './zone-spec/long-stack-trace-zone.spec'; import './zone-spec/async-test.spec'; import './zone-spec/sync-test.spec'; import './zone-spec/fake-async-test.spec'; +import './zone-spec/proxy.spec'; diff --git a/test/zone-spec/proxy.spec.ts b/test/zone-spec/proxy.spec.ts new file mode 100644 index 000000000..c3d2b3fd6 --- /dev/null +++ b/test/zone-spec/proxy.spec.ts @@ -0,0 +1,130 @@ +import '../../lib/zone-spec/proxy'; + +describe('ProxySpec', () => { + let ProxyZoneSpec: any; + let delegate: ZoneSpec; + let proxyZoneSpec: any; + let proxyZone: Zone; + + beforeEach(() => { + ProxyZoneSpec = Zone['ProxyZoneSpec']; + expect(typeof ProxyZoneSpec).toBe('function'); + delegate = {name: 'delegate'}; + proxyZoneSpec = new ProxyZoneSpec(delegate); + proxyZone = Zone.current.fork(proxyZoneSpec); + }); + + describe('properties', () => { + it('should expose ProxyZone in the properties', () => { + expect(proxyZone.get('ProxyZoneSpec')).toBe(proxyZoneSpec); + }); + + it('should assert that it is in or out of ProxyZone', () => { + expect(() => ProxyZoneSpec.assertPresent()).toThrow(); + expect(ProxyZoneSpec.isLoaded()).toBe(false); + expect(ProxyZoneSpec.get()).toBe(undefined); + proxyZone.run(() => { + expect(ProxyZoneSpec.isLoaded()).toBe(true); + expect(() => ProxyZoneSpec.assertPresent()).not.toThrow(); + expect(ProxyZoneSpec.get()).toBe(proxyZoneSpec); + }); + }); + + it('should reset properties', () => { + expect(proxyZone.get('myTestKey')).toBe(undefined); + proxyZoneSpec.setDelegate({name: 'd1', properties: {'myTestKey': 'myTestValue'}}); + expect(proxyZone.get('myTestKey')).toBe('myTestValue'); + proxyZoneSpec.resetDelegate(); + expect(proxyZone.get('myTestKey')).toBe(undefined); + }); + }); + + describe('delegate', () => { + it('should set/reset delegate', () => { + const defaultDelegate: ZoneSpec = {name: 'defaultDelegate'}; + const otherDelegate: ZoneSpec = {name: 'otherDelegate'}; + const proxyZoneSpec = new ProxyZoneSpec(defaultDelegate); + const proxyZone = Zone.current.fork(proxyZoneSpec); + + expect(proxyZoneSpec.getDelegate()).toEqual(defaultDelegate); + + proxyZoneSpec.setDelegate(otherDelegate); + expect(proxyZoneSpec.getDelegate()).toEqual(otherDelegate); + proxyZoneSpec.resetDelegate(); + expect(proxyZoneSpec.getDelegate()).toEqual(defaultDelegate); + }); + }); + + describe('forwarding', () => { + beforeEach(() => { + proxyZoneSpec = new ProxyZoneSpec(); + proxyZone = Zone.current.fork(proxyZoneSpec); + }); + + it('should fork', () => { + const forkeZone = proxyZone.fork({name: 'fork'}); + expect(forkeZone).not.toBe(proxyZone); + expect(forkeZone.name).toBe('fork'); + var called = false; + proxyZoneSpec.setDelegate({ + name: '.', + onFork: (parentZoneDelegate, currentZone, targetZone, zoneSpec) => { + expect(currentZone).toBe(proxyZone); + expect(targetZone).toBe(proxyZone), + expect(zoneSpec.name).toBe('fork2'); + called = true; + } + }); + proxyZone.fork({name: 'fork2'}); + expect(called).toBe(true); + }); + + it('should intercept', () => { + const fn = (a) => a; + expect(proxyZone.wrap(fn, 'test')('works')).toEqual('works'); + proxyZoneSpec.setDelegate({ + name: '.', + onIntercept: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + delegate: Function, source: string): Function => { + return () => '(works)'; + } + }); + expect(proxyZone.wrap(fn, 'test')('works')).toEqual('(works)'); + }); + + it('should invoke', () => { + const fn = () => 'works'; + expect(proxyZone.run(fn)).toEqual('works'); + proxyZoneSpec.setDelegate({ + name: '.', + onInvoke: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + delegate: Function, applyThis: any, applyArgs: any[], source: string) => { + return `(${parentZoneDelegate.invoke(targetZone, delegate, applyThis, applyArgs, source)})`; + } + }); + expect(proxyZone.run(fn)).toEqual('(works)'); + }); + + it('should handleError', () => { + const error = new Error("TestError"); + const fn = () => { throw error }; + expect(() => proxyZone.run(fn)).toThrow(error); + proxyZoneSpec.setDelegate({ + name: '.', + onHandleError: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, + error: any): boolean => { + expect(error).toEqual(error); + return false; + } + }); + expect(() => proxyZone.runGuarded(fn)).not.toThrow(); + }); + + it('should Task', () => { + const fn = () => null; + const task = proxyZone.scheduleMacroTask('test', fn, {}, () => null, () => null); + expect(task.source).toEqual('test'); + proxyZone.cancelTask(task); + }); + }); +});