/** * @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 {isBrowser, isIE, zoneSymbol} from '../../lib/common/utils'; import {ifEnvSupports, isSafari, isSupportSetErrorStack} from '../test-util'; const defineProperty = (Object as any)[zoneSymbol('defineProperty')] || Object.defineProperty; describe( 'longStackTraceZone', ifEnvSupports(isSupportSetErrorStack, function() { let log: Error[]; let lstz: Zone; let longStackTraceZoneSpec = (Zone as any)['longStackTraceZoneSpec']; let defaultTimeout = jasmine.DEFAULT_TIMEOUT_INTERVAL; beforeEach(function() { lstz = Zone.current.fork(longStackTraceZoneSpec).fork({ name: 'long-stack-trace-zone-test', onHandleError: (parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): boolean => { parentZoneDelegate.handleError(targetZone, error); log.push(error); return false; } }); log = []; jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000; }); afterEach(function() { jasmine.DEFAULT_TIMEOUT_INTERVAL = defaultTimeout; }); function expectElapsed(stack: string, expectedCount: number) { try { let actualCount = stack.split('_Elapsed_').length; if (actualCount !== expectedCount) { expect(actualCount).toEqual(expectedCount); console.log(stack); } } catch (e) { expect(e).toBe(null); } } it('should produce long stack traces', function(done) { lstz.run(function() { setTimeout(function() { setTimeout(function() { setTimeout(function() { expectElapsed(log[0].stack !, 3); done(); }, 0); throw new Error('Hello'); }, 0); }, 0); }); }); it('should produce long stack traces for optimized eventTask', ifEnvSupports(() => isBrowser, function() { lstz.run(function() { const button = document.createElement('button'); const clickEvent = document.createEvent('Event'); clickEvent.initEvent('click', true, true); document.body.appendChild(button); button.addEventListener('click', function() { expectElapsed(log[0].stack !, 1); }); button.dispatchEvent(clickEvent); document.body.removeChild(button); }); })); it('should not overwrite long stack traces data for different optimized eventTasks', ifEnvSupports(() => isBrowser, function() { lstz.run(function() { const button = document.createElement('button'); const clickEvent = document.createEvent('Event'); clickEvent.initEvent('click', true, true); document.body.appendChild(button); const div = document.createElement('div'); const enterEvent = document.createEvent('Event'); enterEvent.initEvent('mouseenter', true, true); document.body.appendChild(div); button.addEventListener('click', function() { throw new Error('clickError'); }); div.addEventListener('mouseenter', function() { throw new Error('enterError'); }); button.dispatchEvent(clickEvent); div.dispatchEvent(enterEvent); expect(log.length).toBe(2); if (!isSafari() && !isIE()) { expect(log[0].stack === log[1].stack).toBe(false); } document.body.removeChild(button); document.body.removeChild(div); }); })); it('should produce a long stack trace even if stack setter throws', (done) => { let wasStackAssigned = false; let error = new Error('Expected error'); defineProperty(error, 'stack', { configurable: false, get: () => 'someStackTrace', set: (v: any) => { throw new Error('no writes'); } }); lstz.run(() => { setTimeout(() => { throw error; }); }); setTimeout(() => { const e = log[0]; expect((e as any).longStack).toBeTruthy(); done(); }); }); it('should produce long stack traces when has uncaught error in promise', function(done) { lstz.runGuarded(function() { setTimeout(function() { setTimeout(function() { let promise = new Promise(function(resolve, reject) { setTimeout(function() { reject(new Error('Hello Promise')); }, 0); }); promise.then(function() { fail('should not get here'); }); setTimeout(function() { expectElapsed(log[0].stack !, 5); done(); }, 0); }, 0); }, 0); }); }); it('should produce long stack traces when handling error in promise', function(done) { lstz.runGuarded(function() { setTimeout(function() { setTimeout(function() { let promise = new Promise(function(resolve, reject) { setTimeout(function() { try { throw new Error('Hello Promise'); } catch (err) { reject(err); } }, 0); }); promise.catch(function(error) { // should be able to get long stack trace const longStackFrames: string = longStackTraceZoneSpec.getLongStackTrace(error); expectElapsed(longStackFrames, 4); done(); }); }, 0); }, 0); }); }); it('should not produce long stack traces if Error.stackTraceLimit = 0', function(done) { const originalStackTraceLimit = Error.stackTraceLimit; lstz.run(function() { setTimeout(function() { setTimeout(function() { setTimeout(function() { if (log[0].stack) { expectElapsed(log[0].stack !, 1); } Error.stackTraceLimit = originalStackTraceLimit; done(); }, 0); Error.stackTraceLimit = 0; throw new Error('Hello'); }, 0); }, 0); }); }); }));