272 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
		
		
			
		
	
	
			272 lines
		
	
	
		
			9.5 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| 
								 | 
							
								/**
							 | 
						||
| 
								 | 
							
								 * @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
							 | 
						||
| 
								 | 
							
								 */
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								import {patchMethod, patchProperty, patchPrototype, zoneSymbol} from '../../lib/common/utils';
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								describe('utils', function() {
							 | 
						||
| 
								 | 
							
								  describe('patchMethod', () => {
							 | 
						||
| 
								 | 
							
								    it('should patch target where the method is defined', () => {
							 | 
						||
| 
								 | 
							
								      let args: any[]|undefined;
							 | 
						||
| 
								 | 
							
								      let self: any;
							 | 
						||
| 
								 | 
							
								      class Type {
							 | 
						||
| 
								 | 
							
								        method(..._args: any[]) {
							 | 
						||
| 
								 | 
							
								          args = _args;
							 | 
						||
| 
								 | 
							
								          self = this;
							 | 
						||
| 
								 | 
							
								          return 'OK';
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      const method = Type.prototype.method;
							 | 
						||
| 
								 | 
							
								      let delegateMethod: Function;
							 | 
						||
| 
								 | 
							
								      let delegateSymbol: string;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const instance = new Type();
							 | 
						||
| 
								 | 
							
								      expect(patchMethod(instance, 'method', (delegate: Function, symbol: string, name: string) => {
							 | 
						||
| 
								 | 
							
								        expect(name).toEqual('method');
							 | 
						||
| 
								 | 
							
								        delegateMethod = delegate;
							 | 
						||
| 
								 | 
							
								        delegateSymbol = symbol;
							 | 
						||
| 
								 | 
							
								        return function(self, args) { return delegate.apply(self, ['patch', args[0]]); };
							 | 
						||
| 
								 | 
							
								      })).toBe(delegateMethod !);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      expect(instance.method('a0')).toEqual('OK');
							 | 
						||
| 
								 | 
							
								      expect(args).toEqual(['patch', 'a0']);
							 | 
						||
| 
								 | 
							
								      expect(self).toBe(instance);
							 | 
						||
| 
								 | 
							
								      expect(delegateMethod !).toBe(method);
							 | 
						||
| 
								 | 
							
								      expect(delegateSymbol !).toEqual(zoneSymbol('method'));
							 | 
						||
| 
								 | 
							
								      expect((Type.prototype as any)[delegateSymbol !]).toBe(method);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should not double patch', () => {
							 | 
						||
| 
								 | 
							
								      const Type = function() {};
							 | 
						||
| 
								 | 
							
								      const method = Type.prototype.method = function() {};
							 | 
						||
| 
								 | 
							
								      patchMethod(Type.prototype, 'method', (delegate) => {
							 | 
						||
| 
								 | 
							
								        return function(self, args: any[]) { return delegate.apply(self, ['patch', ...args]); };
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      const pMethod = Type.prototype.method;
							 | 
						||
| 
								 | 
							
								      expect(pMethod).not.toBe(method);
							 | 
						||
| 
								 | 
							
								      patchMethod(Type.prototype, 'method', (delegate) => {
							 | 
						||
| 
								 | 
							
								        return function(self, args) { return delegate.apply(self, ['patch', ...args]); };
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(pMethod).toBe(Type.prototype.method);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('should not patch property which is not configurable', () => {
							 | 
						||
| 
								 | 
							
								      const TestType = function() {};
							 | 
						||
| 
								 | 
							
								      const originalDefineProperty = (Object as any)[zoneSymbol('defineProperty')];
							 | 
						||
| 
								 | 
							
								      if (originalDefineProperty) {
							 | 
						||
| 
								 | 
							
								        originalDefineProperty(
							 | 
						||
| 
								 | 
							
								            TestType.prototype, 'nonConfigurableProperty',
							 | 
						||
| 
								 | 
							
								            {configurable: false, writable: true, value: 'test'});
							 | 
						||
| 
								 | 
							
								      } else {
							 | 
						||
| 
								 | 
							
								        Object.defineProperty(
							 | 
						||
| 
								 | 
							
								            TestType.prototype, 'nonConfigurableProperty',
							 | 
						||
| 
								 | 
							
								            {configurable: false, writable: true, value: 'test'});
							 | 
						||
| 
								 | 
							
								      }
							 | 
						||
| 
								 | 
							
								      patchProperty(TestType.prototype, 'nonConfigurableProperty');
							 | 
						||
| 
								 | 
							
								      const desc = Object.getOwnPropertyDescriptor(TestType.prototype, 'nonConfigurableProperty');
							 | 
						||
| 
								 | 
							
								      expect(desc !.writable).toBeTruthy();
							 | 
						||
| 
								 | 
							
								      expect(!desc !.get).toBeTruthy();
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								  describe('patchPrototype', () => {
							 | 
						||
| 
								 | 
							
								    it('non configurable property desc should be patched', () => {
							 | 
						||
| 
								 | 
							
								      'use strict';
							 | 
						||
| 
								 | 
							
								      const TestFunction: any = function() {};
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      Object.defineProperties(TestFunction.prototype, {
							 | 
						||
| 
								 | 
							
								        'property1': {
							 | 
						||
| 
								 | 
							
								          value: function Property1(callback: Function) { Zone.root.run(callback); },
							 | 
						||
| 
								 | 
							
								          writable: true,
							 | 
						||
| 
								 | 
							
								          configurable: true,
							 | 
						||
| 
								 | 
							
								          enumerable: true
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        'property2': {
							 | 
						||
| 
								 | 
							
								          value: function Property2(callback: Function) { Zone.root.run(callback); },
							 | 
						||
| 
								 | 
							
								          writable: true,
							 | 
						||
| 
								 | 
							
								          configurable: false,
							 | 
						||
| 
								 | 
							
								          enumerable: true
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const zone = Zone.current.fork({name: 'patch'});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property1(() => { log.push('property1' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property1<root>', 'property2<root>']);
							 | 
						||
| 
								 | 
							
								      log.length = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      patchPrototype(TestFunction.prototype, ['property1', 'property2']);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property1(() => { log.push('property1' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property1patch', 'property2patch']);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('non writable property desc should not be patched', () => {
							 | 
						||
| 
								 | 
							
								      'use strict';
							 | 
						||
| 
								 | 
							
								      const TestFunction: any = function() {};
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      Object.defineProperties(TestFunction.prototype, {
							 | 
						||
| 
								 | 
							
								        'property1': {
							 | 
						||
| 
								 | 
							
								          value: function Property1(callback: Function) { Zone.root.run(callback); },
							 | 
						||
| 
								 | 
							
								          writable: true,
							 | 
						||
| 
								 | 
							
								          configurable: true,
							 | 
						||
| 
								 | 
							
								          enumerable: true
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        'property2': {
							 | 
						||
| 
								 | 
							
								          value: function Property2(callback: Function) { Zone.root.run(callback); },
							 | 
						||
| 
								 | 
							
								          writable: false,
							 | 
						||
| 
								 | 
							
								          configurable: true,
							 | 
						||
| 
								 | 
							
								          enumerable: true
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const zone = Zone.current.fork({name: 'patch'});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property1(() => { log.push('property1' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property1<root>', 'property2<root>']);
							 | 
						||
| 
								 | 
							
								      log.length = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      patchPrototype(TestFunction.prototype, ['property1', 'property2']);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property1(() => { log.push('property1' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property1patch', 'property2<root>']);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('readonly property desc should not be patched', () => {
							 | 
						||
| 
								 | 
							
								      'use strict';
							 | 
						||
| 
								 | 
							
								      const TestFunction: any = function() {};
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      Object.defineProperties(TestFunction.prototype, {
							 | 
						||
| 
								 | 
							
								        'property1': {
							 | 
						||
| 
								 | 
							
								          get: function() {
							 | 
						||
| 
								 | 
							
								            if (!this._property1) {
							 | 
						||
| 
								 | 
							
								              this._property1 = function Property2(callback: Function) { Zone.root.run(callback); };
							 | 
						||
| 
								 | 
							
								            }
							 | 
						||
| 
								 | 
							
								            return this._property1;
							 | 
						||
| 
								 | 
							
								          },
							 | 
						||
| 
								 | 
							
								          set: function(func: Function) { this._property1 = func; },
							 | 
						||
| 
								 | 
							
								          configurable: true,
							 | 
						||
| 
								 | 
							
								          enumerable: true
							 | 
						||
| 
								 | 
							
								        },
							 | 
						||
| 
								 | 
							
								        'property2': {
							 | 
						||
| 
								 | 
							
								          get: function() {
							 | 
						||
| 
								 | 
							
								            return function Property2(callback: Function) { Zone.root.run(callback); };
							 | 
						||
| 
								 | 
							
								          },
							 | 
						||
| 
								 | 
							
								          configurable: true,
							 | 
						||
| 
								 | 
							
								          enumerable: true
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const zone = Zone.current.fork({name: 'patch'});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property1(() => { log.push('property1' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property1<root>', 'property2<root>']);
							 | 
						||
| 
								 | 
							
								      log.length = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      patchPrototype(TestFunction.prototype, ['property1', 'property2']);
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property1(() => { log.push('property1' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property1patch', 'property2<root>']);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('non writable method should not be patched', () => {
							 | 
						||
| 
								 | 
							
								      'use strict';
							 | 
						||
| 
								 | 
							
								      const TestFunction: any = function() {};
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      Object.defineProperties(TestFunction.prototype, {
							 | 
						||
| 
								 | 
							
								        'property2': {
							 | 
						||
| 
								 | 
							
								          value: function Property2(callback: Function) { Zone.root.run(callback); },
							 | 
						||
| 
								 | 
							
								          writable: false,
							 | 
						||
| 
								 | 
							
								          configurable: true,
							 | 
						||
| 
								 | 
							
								          enumerable: true
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const zone = Zone.current.fork({name: 'patch'});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property2<root>']);
							 | 
						||
| 
								 | 
							
								      log.length = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      patchMethod(
							 | 
						||
| 
								 | 
							
								          TestFunction.prototype, 'property2',
							 | 
						||
| 
								 | 
							
								          function(delegate: Function, delegateName: string, name: string) {
							 | 
						||
| 
								 | 
							
								            return function(self: any, args: any) { log.push('patched property2'); };
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property2<root>']);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								    it('readonly method should not be patched', () => {
							 | 
						||
| 
								 | 
							
								      'use strict';
							 | 
						||
| 
								 | 
							
								      const TestFunction: any = function() {};
							 | 
						||
| 
								 | 
							
								      const log: string[] = [];
							 | 
						||
| 
								 | 
							
								      Object.defineProperties(TestFunction.prototype, {
							 | 
						||
| 
								 | 
							
								        'property2': {
							 | 
						||
| 
								 | 
							
								          get: function() {
							 | 
						||
| 
								 | 
							
								            return function Property2(callback: Function) { Zone.root.run(callback); };
							 | 
						||
| 
								 | 
							
								          },
							 | 
						||
| 
								 | 
							
								          configurable: true,
							 | 
						||
| 
								 | 
							
								          enumerable: true
							 | 
						||
| 
								 | 
							
								        }
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      const zone = Zone.current.fork({name: 'patch'});
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property2<root>']);
							 | 
						||
| 
								 | 
							
								      log.length = 0;
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      patchMethod(
							 | 
						||
| 
								 | 
							
								          TestFunction.prototype, 'property2',
							 | 
						||
| 
								 | 
							
								          function(delegate: Function, delegateName: string, name: string) {
							 | 
						||
| 
								 | 
							
								            return function(self: any, args: any) { log.push('patched property2'); };
							 | 
						||
| 
								 | 
							
								          });
							 | 
						||
| 
								 | 
							
								
							 | 
						||
| 
								 | 
							
								      zone.run(() => {
							 | 
						||
| 
								 | 
							
								        const instance = new TestFunction();
							 | 
						||
| 
								 | 
							
								        instance.property2(() => { log.push('property2' + Zone.current.name); });
							 | 
						||
| 
								 | 
							
								      });
							 | 
						||
| 
								 | 
							
								      expect(log).toEqual(['property2<root>']);
							 | 
						||
| 
								 | 
							
								    });
							 | 
						||
| 
								 | 
							
								  });
							 | 
						||
| 
								 | 
							
								});
							 |