Update the license headers throughout the repository to reference Google LLC rather than Google Inc, for the required license headers. PR Close #37205
		
			
				
	
	
		
			461 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
			
		
		
	
	
			461 lines
		
	
	
		
			16 KiB
		
	
	
	
		
			TypeScript
		
	
	
	
	
	
| /**
 | |
|  * @license
 | |
|  * Copyright Google LLC 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 {isBrowser} from '../../lib/common/utils';
 | |
| import {isSafari, zoneSymbol} from '../test-util';
 | |
| 
 | |
| // simulate @angular/facade/src/error.ts
 | |
| class BaseError extends Error {
 | |
|   /** @internal **/
 | |
|   _nativeError: Error;
 | |
| 
 | |
|   constructor(message: string) {
 | |
|     super(message);
 | |
|     const nativeError = new Error(message) as any as Error;
 | |
|     this._nativeError = nativeError;
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     return this._nativeError.message;
 | |
|   }
 | |
|   set message(message) {
 | |
|     this._nativeError.message = message;
 | |
|   }
 | |
|   get name() {
 | |
|     return this._nativeError.name;
 | |
|   }
 | |
|   get stack() {
 | |
|     return (this._nativeError as any).stack;
 | |
|   }
 | |
|   set stack(value) {
 | |
|     (this._nativeError as any).stack = value;
 | |
|   }
 | |
|   toString() {
 | |
|     return this._nativeError.toString();
 | |
|   }
 | |
| }
 | |
| 
 | |
| class WrappedError extends BaseError {
 | |
|   originalError: any;
 | |
| 
 | |
|   constructor(message: string, error: any) {
 | |
|     super(`${message} caused by: ${error instanceof Error ? error.message : error}`);
 | |
|     this.originalError = error;
 | |
|   }
 | |
| 
 | |
|   get stack() {
 | |
|     return ((this.originalError instanceof Error ? this.originalError : this._nativeError) as any)
 | |
|         .stack;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class TestError extends WrappedError {
 | |
|   constructor(message: string, error: any) {
 | |
|     super(`${message} caused by: ${error instanceof Error ? error.message : error}`, error);
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     return 'test ' + this.originalError.message;
 | |
|   }
 | |
| }
 | |
| 
 | |
| class TestMessageError extends WrappedError {
 | |
|   constructor(message: string, error: any) {
 | |
|     super(`${message} caused by: ${error instanceof Error ? error.message : error}`, error);
 | |
|   }
 | |
| 
 | |
|   get message() {
 | |
|     return 'test ' + this.originalError.message;
 | |
|   }
 | |
| 
 | |
|   set message(value) {
 | |
|     this.originalError.message = value;
 | |
|   }
 | |
| }
 | |
| 
 | |
| describe('ZoneAwareError', () => {
 | |
|   // If the environment does not supports stack rewrites, then these tests will fail
 | |
|   // and there is no point in running them.
 | |
|   const _global: any = typeof window !== 'undefined' ? window : global;
 | |
|   let config: any;
 | |
|   const __karma__ = _global.__karma__;
 | |
|   if (typeof __karma__ !== 'undefined') {
 | |
|     config = __karma__ && (__karma__ as any).config;
 | |
|   } else if (typeof process !== 'undefined') {
 | |
|     config = process.env;
 | |
|   }
 | |
|   const policy = (config && config['errorpolicy']) || 'default';
 | |
|   if (!(Error as any)['stackRewrite'] && policy !== 'disable') return;
 | |
| 
 | |
|   it('should keep error prototype chain correctly', () => {
 | |
|     class MyError extends Error {}
 | |
|     const myError = new MyError();
 | |
|     expect(myError instanceof Error).toBe(true);
 | |
|     expect(myError instanceof MyError).toBe(true);
 | |
|     expect(myError.stack).not.toBe(undefined);
 | |
|   });
 | |
| 
 | |
|   it('should instanceof error correctly', () => {
 | |
|     let myError = Error('myError');
 | |
|     expect(myError instanceof Error).toBe(true);
 | |
|     let myError1 = Error.call(undefined, 'myError');
 | |
|     expect(myError1 instanceof Error).toBe(true);
 | |
|     let myError2 = Error.call(global, 'myError');
 | |
|     expect(myError2 instanceof Error).toBe(true);
 | |
|     let myError3 = Error.call({}, 'myError');
 | |
|     expect(myError3 instanceof Error).toBe(true);
 | |
|     let myError4 = Error.call({test: 'test'}, 'myError');
 | |
|     expect(myError4 instanceof Error).toBe(true);
 | |
|   });
 | |
| 
 | |
|   it('should return error itself from constructor', () => {
 | |
|     class MyError1 extends Error {
 | |
|       constructor() {
 | |
|         const err: any = super('MyError1');
 | |
|         this.message = err.message;
 | |
|       }
 | |
|     }
 | |
|     let myError1 = new MyError1();
 | |
|     expect(myError1.message).toEqual('MyError1');
 | |
|     expect(myError1.name).toEqual('Error');
 | |
|   });
 | |
| 
 | |
|   it('should return error by calling error directly', () => {
 | |
|     let myError = Error('myError');
 | |
|     expect(myError.message).toEqual('myError');
 | |
|     let myError1 = Error.call(undefined, 'myError');
 | |
|     expect(myError1.message).toEqual('myError');
 | |
|     let myError2 = Error.call(global, 'myError');
 | |
|     expect(myError2.message).toEqual('myError');
 | |
|     let myError3 = Error.call({}, 'myError');
 | |
|     expect(myError3.message).toEqual('myError');
 | |
|   });
 | |
| 
 | |
|   it('should have browser specified property', () => {
 | |
|     let myError = new Error('myError');
 | |
|     if (Object.prototype.hasOwnProperty.call(Error.prototype, 'description')) {
 | |
|       // in IE, error has description property
 | |
|       expect((<any>myError).description).toEqual('myError');
 | |
|     }
 | |
|     if (Object.prototype.hasOwnProperty.call(Error.prototype, 'fileName')) {
 | |
|       // in firefox, error has fileName property
 | |
|       expect((<any>myError).fileName).toBeTruthy();
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   it('should not use child Error class get/set in ZoneAwareError constructor', () => {
 | |
|     const func = () => {
 | |
|       const error = new BaseError('test');
 | |
|       expect(error.message).toEqual('test');
 | |
|     };
 | |
| 
 | |
|     expect(func).not.toThrow();
 | |
|   });
 | |
| 
 | |
|   it('should behave correctly with wrapped error', () => {
 | |
|     const error = new TestError('originalMessage', new Error('error message'));
 | |
|     expect(error.message).toEqual('test error message');
 | |
|     error.originalError.message = 'new error message';
 | |
|     expect(error.message).toEqual('test new error message');
 | |
| 
 | |
|     const error1 = new TestMessageError('originalMessage', new Error('error message'));
 | |
|     expect(error1.message).toEqual('test error message');
 | |
|     error1.message = 'new error message';
 | |
|     expect(error1.message).toEqual('test new error message');
 | |
|   });
 | |
| 
 | |
|   it('should copy customized NativeError properties to ZoneAwareError', () => {
 | |
|     const spy = jasmine.createSpy('errorCustomFunction');
 | |
|     const NativeError = (global as any)[(Zone as any).__symbol__('Error')];
 | |
|     NativeError.customFunction = function(args: any) {
 | |
|       spy(args);
 | |
|     };
 | |
|     expect((Error as any)['customProperty']).toBe('customProperty');
 | |
|     expect(typeof (Error as any)['customFunction']).toBe('function');
 | |
|     (Error as any)['customFunction']('test');
 | |
|     expect(spy).toHaveBeenCalledWith('test');
 | |
|   });
 | |
| 
 | |
|   it('should always have stack property even without throw', () => {
 | |
|     // in IE, the stack will be undefined without throw
 | |
|     // in ZoneAwareError, we will make stack always be
 | |
|     // there event without throw
 | |
|     const error = new Error('test');
 | |
|     const errorWithoutNew = Error('test');
 | |
|     expect(error.stack!.split('\n').length > 0).toBeTruthy();
 | |
|     expect(errorWithoutNew.stack!.split('\n').length > 0).toBeTruthy();
 | |
|   });
 | |
| 
 | |
|   it('should show zone names in stack frames and remove extra frames', () => {
 | |
|     if (policy === 'disable' || !(Error as any)['stackRewrite']) {
 | |
|       return;
 | |
|     }
 | |
|     if (isBrowser && isSafari()) {
 | |
|       return;
 | |
|     }
 | |
|     const rootZone = Zone.root;
 | |
|     const innerZone = rootZone.fork({name: 'InnerZone'});
 | |
| 
 | |
|     rootZone.run(testFn);
 | |
|     function testFn() {
 | |
|       let outside: any;
 | |
|       let inside: any;
 | |
|       let outsideWithoutNew: any;
 | |
|       let insideWithoutNew: any;
 | |
|       try {
 | |
|         throw new Error('Outside');
 | |
|       } catch (e) {
 | |
|         outside = e;
 | |
|       }
 | |
|       try {
 | |
|         throw Error('Outside');
 | |
|       } catch (e) {
 | |
|         outsideWithoutNew = e;
 | |
|       }
 | |
|       innerZone.run(function insideRun() {
 | |
|         try {
 | |
|           throw new Error('Inside');
 | |
|         } catch (e) {
 | |
|           inside = e;
 | |
|         }
 | |
|         try {
 | |
|           throw Error('Inside');
 | |
|         } catch (e) {
 | |
|           insideWithoutNew = e;
 | |
|         }
 | |
|       });
 | |
| 
 | |
|       if (policy === 'lazy') {
 | |
|         outside.stack = outside.zoneAwareStack;
 | |
|         outsideWithoutNew.stack = outsideWithoutNew.zoneAwareStack;
 | |
|         inside.stack = inside.zoneAwareStack;
 | |
|         insideWithoutNew.stack = insideWithoutNew.zoneAwareStack;
 | |
|       }
 | |
| 
 | |
|       expect(outside.stack).toEqual(outside.zoneAwareStack);
 | |
|       expect(outsideWithoutNew.stack).toEqual(outsideWithoutNew.zoneAwareStack);
 | |
|       expect(inside!.stack).toEqual(inside!.zoneAwareStack);
 | |
|       expect(insideWithoutNew!.stack).toEqual(insideWithoutNew!.zoneAwareStack);
 | |
|       expect(typeof inside!.originalStack).toEqual('string');
 | |
|       expect(typeof insideWithoutNew!.originalStack).toEqual('string');
 | |
|       const outsideFrames = outside.stack!.split(/\n/);
 | |
|       const insideFrames = inside!.stack!.split(/\n/);
 | |
|       const outsideWithoutNewFrames = outsideWithoutNew!.stack!.split(/\n/);
 | |
|       const insideWithoutNewFrames = insideWithoutNew!.stack!.split(/\n/);
 | |
| 
 | |
|       // throw away first line if it contains the error
 | |
|       if (/Outside/.test(outsideFrames[0])) {
 | |
|         outsideFrames.shift();
 | |
|       }
 | |
|       if (/Error /.test(outsideFrames[0])) {
 | |
|         outsideFrames.shift();
 | |
|       }
 | |
| 
 | |
|       if (/Outside/.test(outsideWithoutNewFrames[0])) {
 | |
|         outsideWithoutNewFrames.shift();
 | |
|       }
 | |
|       if (/Error /.test(outsideWithoutNewFrames[0])) {
 | |
|         outsideWithoutNewFrames.shift();
 | |
|       }
 | |
| 
 | |
|       if (/Inside/.test(insideFrames[0])) {
 | |
|         insideFrames.shift();
 | |
|       }
 | |
|       if (/Error /.test(insideFrames[0])) {
 | |
|         insideFrames.shift();
 | |
|       }
 | |
| 
 | |
|       if (/Inside/.test(insideWithoutNewFrames[0])) {
 | |
|         insideWithoutNewFrames.shift();
 | |
|       }
 | |
|       if (/Error /.test(insideWithoutNewFrames[0])) {
 | |
|         insideWithoutNewFrames.shift();
 | |
|       }
 | |
|       expect(outsideFrames[0]).toMatch(/testFn.*[<root>]/);
 | |
| 
 | |
|       expect(insideFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
 | |
|       expect(insideFrames[1]).toMatch(/testFn.*[<root>]]/);
 | |
| 
 | |
|       expect(outsideWithoutNewFrames[0]).toMatch(/testFn.*[<root>]/);
 | |
| 
 | |
|       expect(insideWithoutNewFrames[0]).toMatch(/insideRun.*[InnerZone]]/);
 | |
|       expect(insideWithoutNewFrames[1]).toMatch(/testFn.*[<root>]]/);
 | |
|     }
 | |
|   });
 | |
| 
 | |
|   const zoneAwareFrames = [
 | |
|     'Zone.run', 'Zone.runGuarded', 'Zone.scheduleEventTask', 'Zone.scheduleMicroTask',
 | |
|     'Zone.scheduleMacroTask', 'Zone.runTask', 'ZoneDelegate.scheduleTask',
 | |
|     'ZoneDelegate.invokeTask', 'zoneAwareAddListener', 'Zone.prototype.run',
 | |
|     'Zone.prototype.runGuarded', 'Zone.prototype.scheduleEventTask',
 | |
|     'Zone.prototype.scheduleMicroTask', 'Zone.prototype.scheduleMacroTask',
 | |
|     'Zone.prototype.runTask', 'ZoneDelegate.prototype.scheduleTask',
 | |
|     'ZoneDelegate.prototype.invokeTask', 'ZoneTask.invokeTask'
 | |
|   ];
 | |
| 
 | |
|   function assertStackDoesNotContainZoneFrames(err: any) {
 | |
|     const frames = policy === 'lazy' ? err.zoneAwareStack.split('\n') : err.stack.split('\n');
 | |
|     if (policy === 'disable') {
 | |
|       let hasZoneStack = false;
 | |
|       for (let i = 0; i < frames.length; i++) {
 | |
|         if (hasZoneStack) {
 | |
|           break;
 | |
|         }
 | |
|         hasZoneStack = zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1).length > 0;
 | |
|       }
 | |
|       if (!hasZoneStack) {
 | |
|         console.log('stack', err.originalStack);
 | |
|       }
 | |
|       expect(hasZoneStack).toBe(true);
 | |
|     } else {
 | |
|       for (let i = 0; i < frames.length; i++) {
 | |
|         expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]);
 | |
|       }
 | |
|     }
 | |
|   };
 | |
| 
 | |
|   const errorZoneSpec = {
 | |
|     name: 'errorZone',
 | |
|     done: <(() => void)|null>null,
 | |
|     onHandleError:
 | |
|         (parentDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: Error) => {
 | |
|           assertStackDoesNotContainZoneFrames(error);
 | |
|           setTimeout(() => {
 | |
|             errorZoneSpec.done && errorZoneSpec.done();
 | |
|           }, 0);
 | |
|           return false;
 | |
|         }
 | |
|   };
 | |
| 
 | |
|   const errorZone = Zone.root.fork(errorZoneSpec);
 | |
| 
 | |
|   const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) {
 | |
|     return function(done: () => void) {
 | |
|       errorZoneSpec.done = done;
 | |
|       errorZone.run(testFn);
 | |
|     };
 | |
|   };
 | |
| 
 | |
|   describe('Error stack', () => {
 | |
|     it('Error with new which occurs in setTimeout callback should not have zone frames visible',
 | |
|        assertStackDoesNotContainZoneFramesTest(() => {
 | |
|          setTimeout(() => {
 | |
|            throw new Error('timeout test error');
 | |
|          }, 10);
 | |
|        }));
 | |
| 
 | |
|     it('Error without new which occurs in setTimeout callback should not have zone frames visible',
 | |
|        assertStackDoesNotContainZoneFramesTest(() => {
 | |
|          setTimeout(() => {
 | |
|            throw Error('test error');
 | |
|          }, 10);
 | |
|        }));
 | |
| 
 | |
|     it('Error with new which cause by promise rejection should not have zone frames visible',
 | |
|        (done) => {
 | |
|          const p = new Promise((resolve, reject) => {
 | |
|            setTimeout(() => {
 | |
|              reject(new Error('test error'));
 | |
|            });
 | |
|          });
 | |
|          p.catch(err => {
 | |
|            assertStackDoesNotContainZoneFrames(err);
 | |
|            done();
 | |
|          });
 | |
|        });
 | |
| 
 | |
|     it('Error without new which cause by promise rejection should not have zone frames visible',
 | |
|        (done) => {
 | |
|          const p = new Promise((resolve, reject) => {
 | |
|            setTimeout(() => {
 | |
|              reject(Error('test error'));
 | |
|            });
 | |
|          });
 | |
|          p.catch(err => {
 | |
|            assertStackDoesNotContainZoneFrames(err);
 | |
|            done();
 | |
|          });
 | |
|        });
 | |
| 
 | |
|     it('Error with new which occurs in eventTask callback should not have zone frames visible',
 | |
|        assertStackDoesNotContainZoneFramesTest(() => {
 | |
|          const task = Zone.current.scheduleEventTask('errorEvent', () => {
 | |
|            throw new Error('test error');
 | |
|          }, undefined, () => null, undefined);
 | |
|          task.invoke();
 | |
|        }));
 | |
| 
 | |
|     it('Error without new which occurs in eventTask callback should not have zone frames visible',
 | |
|        assertStackDoesNotContainZoneFramesTest(() => {
 | |
|          const task = Zone.current.scheduleEventTask('errorEvent', () => {
 | |
|            throw Error('test error');
 | |
|          }, undefined, () => null, undefined);
 | |
|          task.invoke();
 | |
|        }));
 | |
| 
 | |
|     it('Error with new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
 | |
|        assertStackDoesNotContainZoneFramesTest(() => {
 | |
|          const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
 | |
|                           .scheduleEventTask('errorEvent', () => {
 | |
|                             throw new Error('test error');
 | |
|                           }, undefined, () => null, undefined);
 | |
|          task.invoke();
 | |
|        }));
 | |
| 
 | |
|     it('Error without new which occurs in longStackTraceZone should not have zone frames and longStackTraceZone frames visible',
 | |
|        assertStackDoesNotContainZoneFramesTest(() => {
 | |
|          const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
 | |
|                           .scheduleEventTask('errorEvent', () => {
 | |
|                             throw Error('test error');
 | |
|                           }, undefined, () => null, undefined);
 | |
|          task.invoke();
 | |
|        }));
 | |
| 
 | |
|     it('stack frames of the callback in user customized zoneSpec should be kept',
 | |
|        assertStackDoesNotContainZoneFramesTest(() => {
 | |
|          const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec'])
 | |
|                           .fork({
 | |
|                             name: 'customZone',
 | |
|                             onScheduleTask: (parentDelegate, currentZone, targetZone, task) => {
 | |
|                               return parentDelegate.scheduleTask(targetZone, task);
 | |
|                             },
 | |
|                             onHandleError: (parentDelegate, currentZone, targetZone, error) => {
 | |
|                               parentDelegate.handleError(targetZone, error);
 | |
|                               const containsCustomZoneSpecStackTrace =
 | |
|                                   error.stack.indexOf('onScheduleTask') !== -1;
 | |
|                               expect(containsCustomZoneSpecStackTrace).toBeTruthy();
 | |
|                               return false;
 | |
|                             }
 | |
|                           })
 | |
|                           .scheduleEventTask('errorEvent', () => {
 | |
|                             throw new Error('test error');
 | |
|                           }, undefined, () => null, undefined);
 | |
|          task.invoke();
 | |
|        }));
 | |
| 
 | |
|     it('should be able to generate zone free stack even NativeError stack is readonly', function() {
 | |
|       const _global: any =
 | |
|           typeof window === 'object' && window || typeof self === 'object' && self || global;
 | |
|       const NativeError = _global[zoneSymbol('Error')];
 | |
|       const desc = Object.getOwnPropertyDescriptor(NativeError.prototype, 'stack');
 | |
|       if (desc) {
 | |
|         const originalSet: ((value: any) => void)|undefined = desc.set;
 | |
|         // make stack readonly
 | |
|         desc.set = null as any;
 | |
| 
 | |
|         try {
 | |
|           const error = new Error('test error');
 | |
|           expect(error.stack).toBeTruthy();
 | |
|           assertStackDoesNotContainZoneFrames(error);
 | |
|         } finally {
 | |
|           desc.set = originalSet;
 | |
|         }
 | |
|       }
 | |
|     });
 | |
|   });
 | |
| });
 |