From bcd09a0b770d70ea36dffe662dda2b9a086287e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mis=CC=8Cko=20Hevery?= Date: Wed, 15 Mar 2017 17:16:21 -0700 Subject: [PATCH] fix: stack rewriting now works with source maps MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit In Chrome for the source maps to work the object must be an instance of native `Error`. This means that we can’t return a subclass of `Error` or error handling in the dev console will not work properly. In addition the stack frames must have a certain format or the source mapping is disabled. For this reason we have changed the long stack format to conform to that shape. --- lib/browser/browser.ts | 2 +- lib/common/timers.ts | 3 +- lib/common/utils.ts | 3 +- lib/zone-spec/long-stack-trace.ts | 63 +++---- lib/zone.ts | 160 ++--------------- test/browser/element.spec.ts | 3 +- test/common/Error.spec.ts | 173 ++++++------------- test/common/setInterval.spec.ts | 1 + test/common/setTimeout.spec.ts | 1 + test/common/task.spec.ts | 132 +++++--------- test/main.ts | 2 +- test/zone-spec/long-stack-trace-zone.spec.ts | 6 +- 12 files changed, 153 insertions(+), 396 deletions(-) diff --git a/lib/browser/browser.ts b/lib/browser/browser.ts index d1aaed6ee..785e0b86a 100644 --- a/lib/browser/browser.ts +++ b/lib/browser/browser.ts @@ -18,7 +18,7 @@ const set = 'set'; const clear = 'clear'; const blockingMethods = ['alert', 'prompt', 'confirm']; const _global: any = - typeof window === 'object' && window || typeof self === 'object' && self || global; + typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global; patchTimer(_global, set, clear, 'Timeout'); patchTimer(_global, set, clear, 'Interval'); diff --git a/lib/common/timers.ts b/lib/common/timers.ts index 7a9c240ac..3861f6684 100644 --- a/lib/common/timers.ts +++ b/lib/common/timers.ts @@ -23,13 +23,14 @@ export function patchTimer(window: any, setName: string, cancelName: string, nam function scheduleTask(task: Task) { const data = task.data; - data.args[0] = function() { + function timer() { try { task.invoke.apply(this, arguments); } finally { delete tasksByHandleId[data.handleId]; } }; + data.args[0] = timer; data.handleId = setNative.apply(window, data.args); tasksByHandleId[data.handleId] = task; return task; diff --git a/lib/common/utils.ts b/lib/common/utils.ts index 864d0f54c..012344c98 100644 --- a/lib/common/utils.ts +++ b/lib/common/utils.ts @@ -15,7 +15,8 @@ declare const WorkerGlobalScope: any; export const zoneSymbol: (name: string) => string = (n) => `__zone_symbol__${n}`; -const _global: any = typeof window === 'object' && window || typeof self === 'object' && self || global; +const _global: any = + typeof window === 'object' && window || typeof self === 'object' && self || global; export function bindArguments(args: any[], source: string): any[] { for (let i = args.length - 1; i >= 0; i--) { diff --git a/lib/zone-spec/long-stack-trace.ts b/lib/zone-spec/long-stack-trace.ts index 3ad1bb760..d7e987276 100644 --- a/lib/zone-spec/long-stack-trace.ts +++ b/lib/zone-spec/long-stack-trace.ts @@ -11,9 +11,11 @@ */ const NEWLINE = '\n'; -const SEP = ' ------------- '; -const IGNORE_FRAMES: string[] = []; +const IGNORE_FRAMES: {[k: string]: true} = {}; const creationTrace = '__creationTrace__'; +const ERROR_TAG = 'STACKTRACE TRACKING'; +const SEP_TAG = '__SEP_TAG__'; +let sepTemplate = ''; class LongStackTrace { error: Error = getStacktrace(); @@ -21,7 +23,7 @@ class LongStackTrace { } function getStacktraceWithUncaughtError(): Error { - return new Error('STACKTRACE TRACKING'); + return new Error(ERROR_TAG); } function getStacktraceWithCaughtError(): Error { @@ -49,22 +51,24 @@ function addErrorStack(lines: string[], error: Error): void { for (let i = 0; i < trace.length; i++) { const frame = trace[i]; // Filter out the Frames which are part of stack capturing. - if (!(i < IGNORE_FRAMES.length && IGNORE_FRAMES[i] === frame)) { + if (!IGNORE_FRAMES.hasOwnProperty(frame)) { lines.push(trace[i]); } } } function renderLongStackTrace(frames: LongStackTrace[], stack: string): string { - const longTrace: string[] = [stack]; + const longTrace: string[] = [stack.trim()]; if (frames) { let timestamp = new Date().getTime(); for (let i = 0; i < frames.length; i++) { const traceFrames: LongStackTrace = frames[i]; const lastTime = traceFrames.timestamp; - longTrace.push( - `${SEP} Elapsed: ${timestamp - lastTime.getTime()} ms; At: ${lastTime} ${SEP}`); + let separator = + `____________________Elapsed ${timestamp - lastTime.getTime()} ms; At: ${lastTime}`; + separator = separator.replace(/[^\w\d]/g, '_'); + longTrace.push(sepTemplate.replace(SEP_TAG, separator)); addErrorStack(longTrace, traceFrames.error); timestamp = lastTime.getTime(); @@ -105,42 +109,15 @@ function renderLongStackTrace(frames: LongStackTrace[], stack: string): string { }, onHandleError: function( - parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): any { + parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): boolean { const parentTask = Zone.currentTask || error.task; if (error instanceof Error && parentTask) { - let stackSetSucceeded: string|boolean = null; + const longStack = + renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack); try { - let descriptor = Object.getOwnPropertyDescriptor(error, 'stack'); - if (descriptor && descriptor.configurable) { - const delegateGet = descriptor.get; - const value = descriptor.value; - descriptor = { - get: function() { - return renderLongStackTrace( - parentTask.data && parentTask.data[creationTrace], - delegateGet ? delegateGet.apply(this) : value); - } - }; - Object.defineProperty(error, 'stack', descriptor); - stackSetSucceeded = true; - } + error.stack = (error as any).longStack = longStack; } catch (err) { } - const longStack: string = stackSetSucceeded ? - null : - renderLongStackTrace(parentTask.data && parentTask.data[creationTrace], error.stack); - if (!stackSetSucceeded) { - try { - stackSetSucceeded = error.stack = longStack; - } catch (err) { - } - } - if (!stackSetSucceeded) { - try { - stackSetSucceeded = (error as any).longStack = longStack; - } catch (err) { - } - } } return parentZoneDelegate.handleError(targetZone, error); } @@ -161,11 +138,19 @@ function computeIgnoreFrames() { for (let i = 0; i < frames1.length; i++) { const frame1 = frames1[i]; const frame2 = frames2[i]; + if (!sepTemplate && frame1.indexOf(ERROR_TAG) == -1) { + sepTemplate = frame1.replace(/^(\s*(at)?\s*)([\w\/\<]+)/, '$1' + SEP_TAG); + } if (frame1 === frame2) { - IGNORE_FRAMES.push(frame1); + IGNORE_FRAMES[frame1] = true; } else { break; } + console.log('>>>>>>', sepTemplate, frame1); + } + if (!sepTemplate) { + // If we could not find it default to this text. + sepTemplate = SEP_TAG + '@[native code]'; } } computeIgnoreFrames(); diff --git a/lib/zone.ts b/lib/zone.ts index 88925b411..5dfc391fd 100644 --- a/lib/zone.ts +++ b/lib/zone.ts @@ -877,7 +877,7 @@ const Zone: ZoneType = (function(global: any) { private _taskCounts: {microTask: number, macroTask: number, - eventTask: number} = {microTask: 0, macroTask: 0, eventTask: 0}; + eventTask: number} = {'microTask': 0, 'macroTask': 0, 'eventTask': 0}; private _parentDelegate: ZoneDelegate; @@ -1052,7 +1052,7 @@ const Zone: ZoneType = (function(global: any) { } cancelTask(targetZone: Zone, task: Task): any { - let value; + let value: any; if (this._cancelTaskZS) { value = this._cancelTaskZS.onCancelTask( this._cancelTaskDlgt, this._cancelTaskCurrZone, targetZone, task); @@ -1078,8 +1078,8 @@ const Zone: ZoneType = (function(global: any) { _updateTaskCount(type: TaskType, count: number) { const counts = this._taskCounts; - const prev = counts[type]; - const next = counts[type] = prev + count; + const prev = (counts as any)[type]; + const next = (counts as any)[type] = prev + count; if (next < 0) { throw new Error('More tasks executed then were scheduled.'); } @@ -1347,7 +1347,7 @@ const Zone: ZoneType = (function(global: any) { } if ((promise as any)[symbolState] === UNRESOLVED) { // should only get value.then once based on promise spec. - let then = null; + let then: any = null; try { if (typeof value === 'object' || typeof value === 'function') { then = value && value.then; @@ -1622,122 +1622,6 @@ const Zone: ZoneType = (function(global: any) { global.Error = ZoneAwareError; const stackRewrite = 'stackRewrite'; - // fix #595, create property descriptor - // for error properties - const createProperty = function(props: {[k: string]: any}, key: string) { - // if property is already defined, skip it. - if (props[key]) { - return; - } - // define a local property - // in case error property is not settable - const name = __symbol__(key); - props[key] = { - configurable: true, - enumerable: true, - get: function() { - // if local property has no value - // use internal error's property value - if (!this[name]) { - const error = this[__symbol__('error')]; - if (error) { - this[name] = error[key]; - } - } - return this[name]; - }, - set: function(value: any) { - // setter will set value to local property value - this[name] = value; - } - }; - }; - - // fix #595, create property descriptor - // for error method properties - const createMethodProperty = function(props: {[k: string]: any}, key: string) { - if (props[key]) { - return; - } - props[key] = { - configurable: true, - enumerable: true, - writable: true, - value: function() { - const error = this[__symbol__('error')]; - let errorMethod = (error && error[key]) || this[key]; - if (errorMethod) { - return errorMethod.apply(error, arguments); - } - } - }; - }; - - const createErrorProperties = function() { - const props = Object.create(null); - - const error = new NativeError(); - let keys = Object.getOwnPropertyNames(error); - for (let i = 0; i < keys.length; i++) { - const key = keys[i]; - // Avoid bugs when hasOwnProperty is shadowed - if (Object.prototype.hasOwnProperty.call(error, key)) { - createProperty(props, key); - } - } - - const proto = NativeError.prototype; - if (proto) { - let pKeys = Object.getOwnPropertyNames(proto); - for (let i = 0; i < pKeys.length; i++) { - const key = pKeys[i]; - // skip constructor - if (key !== 'constructor' && key !== 'toString' && key !== 'toSource') { - createProperty(props, key); - } - } - } - - // some other properties are not - // in NativeError - createProperty(props, 'originalStack'); - createProperty(props, 'zoneAwareStack'); - // in IE, stack is not in prototype - createProperty(props, 'stack'); - - // define toString, toSource as method property - createMethodProperty(props, 'toString'); - createMethodProperty(props, 'toSource'); - return props; - }; - - const errorProperties = createErrorProperties(); - - // for derived Error class which extends ZoneAwareError - // we should not override the derived class's property - // so we create a new props object only copy the properties - // from errorProperties which not exist in derived Error's prototype - const getErrorPropertiesForPrototype = function(prototype: any) { - // if the prototype is ZoneAwareError.prototype - // we just return the prebuilt errorProperties. - if (prototype === ZoneAwareError.prototype) { - return errorProperties; - } - const newProps = Object.create(null); - const cKeys = Object.getOwnPropertyNames(errorProperties); - const keys = Object.getOwnPropertyNames(prototype); - cKeys.forEach(cKey => { - if (keys.filter(key => { - return key === cKey; - }) - .length === 0) { - newProps[cKey] = errorProperties[cKey]; - } - }); - - return newProps; - }; - // some functions are not easily to be detected here, // for example Timeout.ZoneTask.invoke, if we want to detect those functions // by detect zone, we have to run all patched APIs, it is too risky @@ -1747,7 +1631,7 @@ const Zone: ZoneType = (function(global: any) { 'long-stack-trace' ]; - function attachZoneAndRemoveInternalZoneFrames(error: any, zoneAwareError: any) { + function attachZoneAndRemoveInternalZoneFrames(error: Error) { // Save original stack trace error.originalStack = error.stack; // Process the stack trace and rewrite the frames. @@ -1795,7 +1679,6 @@ const Zone: ZoneType = (function(global: any) { } catch (nonWritableErr) { // in some browser, the error.stack is readonly such as PhantomJS // so we need to store the stack frames to zoneAwareError directly - zoneAwareError.stack = finalStack; } } } @@ -1805,14 +1688,7 @@ const Zone: ZoneType = (function(global: any) { * adds zone information to it. */ function ZoneAwareError() { - // make sure we have a valid this - // if this is undefined(call Error without new) or this is global - // or this is some other objects, we should force to create a - // valid ZoneAwareError by call Object.create() - if (!(this instanceof ZoneAwareError)) { - return ZoneAwareError.apply(Object.create(ZoneAwareError.prototype), arguments); - } - // Create an Error. + // We always have to return native error otherwise the browser console will not work. let error: Error = NativeError.apply(this, arguments); if (!error.stack) { // in IE, the error.stack will be undefined @@ -1824,16 +1700,10 @@ const Zone: ZoneType = (function(global: any) { error = err; } } - this[__symbol__('error')] = error; // 1. attach zone information to stack frame // 2. remove zone internal stack frames - attachZoneAndRemoveInternalZoneFrames(error, this); - - // use defineProperties here instead of copy property value - // because of issue #595 which will break angular2. - const props = getErrorPropertiesForPrototype(Object.getPrototypeOf(this)); - Object.defineProperties(this, props); - return this; + attachZoneAndRemoveInternalZoneFrames(error); + return error; } // Copy the prototype so that instanceof operator works as expected @@ -1975,8 +1845,7 @@ const Zone: ZoneType = (function(global: any) { // use this method to handle // 1. IE issue, the error.stack can only be not undefined after throw // 2. handle Error(...) without new options - const throwError = (message: string, withNew: boolean = true) => { - let error; + const throwError = (message: string, withNew?: boolean) => { try { if (withNew) { throw new Error(message); @@ -1984,9 +1853,8 @@ const Zone: ZoneType = (function(global: any) { throw Error(message); } } catch (err) { - error = err; + return err; } - return error; }; const nativeStackTraceLimit = NativeError.stackTraceLimit; @@ -2000,7 +1868,7 @@ const Zone: ZoneType = (function(global: any) { let detectRunFn = () => { detectZone.run(() => { detectZone.runGuarded(() => { - throw throwError('blacklistStackFrames'); + throw throwError('blacklistStackFrames', true); }); }); }; @@ -2008,7 +1876,7 @@ const Zone: ZoneType = (function(global: any) { let detectRunWithoutNewFn = () => { detectZone.run(() => { detectZone.runGuarded(() => { - throw throwError('blacklistStackFrames', false); + throw throwError('blacklistStackFrames'); }); }); }; @@ -2190,4 +2058,4 @@ const Zone: ZoneType = (function(global: any) { NativeError.stackTraceLimit = nativeStackTraceLimit; return global['Zone'] = Zone; -})(typeof window === 'object' && window || typeof self === 'object' && self || global); +})(typeof window !== 'undefined' && window || typeof self !== 'undefined' && self || global); diff --git a/test/browser/element.spec.ts b/test/browser/element.spec.ts index 9b55e316d..15379f9a8 100644 --- a/test/browser/element.spec.ts +++ b/test/browser/element.spec.ts @@ -7,7 +7,6 @@ */ import {ifEnvSupports} from '../test-util'; -declare const global: any; describe('element', function() { let button: HTMLButtonElement; @@ -92,7 +91,7 @@ describe('element', function() { * For now we are choosing to ignore it and assume that this arrises in tests only. * As an added measure we make sure that all jasmine tests always run in a task. See: jasmine.ts */ - global[(Zone as any).__symbol__('setTimeout')](() => { + (window as any)[(Zone as any).__symbol__('setTimeout')](() => { let log = ''; button.addEventListener('click', () => { Zone.current.scheduleMicroTask('test', () => log += 'microtask;'); diff --git a/test/common/Error.spec.ts b/test/common/Error.spec.ts index 0f45eed65..70c0eeaba 100644 --- a/test/common/Error.spec.ts +++ b/test/common/Error.spec.ts @@ -6,74 +6,8 @@ * found in the LICENSE file at https://angular.io/license */ -// 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; - } -} +const _global: any = + typeof window === 'object' && window || typeof self === 'object' && self || global; describe('ZoneAwareError', () => { // If the environment does not supports stack rewrites, then these tests will fail @@ -84,7 +18,7 @@ describe('ZoneAwareError', () => { class MyError extends Error {} const myError = new MyError(); expect(myError instanceof Error).toBe(true); - expect(myError instanceof MyError).toBe(true); + expect(myError instanceof _global[(Zone as any).__symbol__('Error')]).toBe(true); expect(myError.stack).not.toBe(undefined); }); @@ -136,27 +70,6 @@ describe('ZoneAwareError', () => { } }); - 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')]; @@ -227,40 +140,28 @@ describe('ZoneAwareError', () => { if (/Outside/.test(outsideFrames[0])) { outsideFrames.shift(); } - if (/new Error/.test(outsideFrames[0])) { + if (/Error /.test(outsideFrames[0])) { outsideFrames.shift(); } if (/Outside/.test(outsideWithoutNewFrames[0])) { outsideWithoutNewFrames.shift(); } - if (/new Error/.test(outsideWithoutNewFrames[0])) { - outsideWithoutNewFrames.shift(); - } - if (/Error.ZoneAwareError/.test(outsideWithoutNewFrames[0])) { - outsideWithoutNewFrames.shift(); - } - if (/ZoneAwareError/.test(outsideWithoutNewFrames[0])) { + if (/Error /.test(outsideWithoutNewFrames[0])) { outsideWithoutNewFrames.shift(); } if (/Inside/.test(insideFrames[0])) { insideFrames.shift(); } - if (/new Error/.test(insideFrames[0])) { + if (/Error /.test(insideFrames[0])) { insideFrames.shift(); } if (/Inside/.test(insideWithoutNewFrames[0])) { insideWithoutNewFrames.shift(); } - if (/new Error/.test(insideWithoutNewFrames[0])) { - insideWithoutNewFrames.shift(); - } - if (/Error.ZoneAwareError/.test(insideWithoutNewFrames[0])) { - insideWithoutNewFrames.shift(); - } - if (/ZoneAwareError/.test(insideWithoutNewFrames[0])) { + if (/Error /.test(insideWithoutNewFrames[0])) { insideWithoutNewFrames.shift(); } @@ -289,7 +190,7 @@ function assertStackDoesNotContainZoneFrames(err: Error) { for (let i = 0; i < frames.length; i++) { expect(zoneAwareFrames.filter(f => frames[i].indexOf(f) !== -1)).toEqual([]); } -}; +} const errorZoneSpec = { name: 'errorZone', @@ -313,6 +214,9 @@ const assertStackDoesNotContainZoneFramesTest = function(testFn: Function) { }; }; +const LongStackTraceZoneSpec: {getLongStackTrace(error: Error): string}&ZoneSpec = + (Zone as any)['longStackTraceZoneSpec']; + describe('Error stack', () => { it('Error with new which occurs in setTimeout callback should not have zone frames visible', assertStackDoesNotContainZoneFramesTest(() => { @@ -368,25 +272,60 @@ describe('Error stack', () => { 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'); - }, null, () => null, null); + const task = + Zone.current.fork(LongStackTraceZoneSpec).scheduleEventTask('errorEvent', () => { + throw new Error('test error'); + }, null, () => null, null); 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'); - }, null, () => null, null); + const task = + Zone.current.fork(LongStackTraceZoneSpec).scheduleEventTask('errorEvent', () => { + throw Error('test error'); + }, null, () => null, null); task.invoke(); })); + it('Error stack trace should have consistent format', (done) => { + const CHROME_FRAMES = /^ at\s/; + const OTHER_FRAMES = /[\w\d.]+/; + + Zone.current.fork(LongStackTraceZoneSpec) + .fork({ + name: 'myTest', + onHandleError: function( + parentZoneDelegate: ZoneDelegate, currentZone: Zone, targetZone: Zone, error: any): + boolean { + parentZoneDelegate.handleError(targetZone, error); + let frames: string[] = error.stack.split('\n'); + if (frames[0].indexOf('LongStackTrace') != -1) frames.shift(); + let frameRegexp: RegExp = null; + if (frames[0].indexOf(' at') == 0) + frameRegexp = CHROME_FRAMES; + else + frameRegexp = OTHER_FRAMES; + for (let i = 0; i < frames.length; i++) { + let frame = frames[i]; + if (frame) { + expect(frame).toMatch(frameRegexp); + } + } + done(); + return false; + } + }) + .run(() => { + setTimeout(() => { + throw new Error('LongStackTrace'); + }, 0); + }); + }); + it('stack frames of the callback in user customized zoneSpec should be kept', assertStackDoesNotContainZoneFramesTest(() => { - const task = Zone.current.fork((Zone as any)['longStackTraceZoneSpec']) + const task = Zone.current.fork(LongStackTraceZoneSpec) .fork({ name: 'customZone', onScheduleTask: (parentDelegate, currentZone, targetZone, task) => { @@ -407,8 +346,6 @@ describe('Error stack', () => { })); 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['__zone_symbol__Error']; const desc = Object.getOwnPropertyDescriptor(NativeError.prototype, 'stack'); if (desc) { diff --git a/test/common/setInterval.spec.ts b/test/common/setInterval.spec.ts index 29b8657b9..552680f4f 100644 --- a/test/common/setInterval.spec.ts +++ b/test/common/setInterval.spec.ts @@ -56,6 +56,7 @@ describe('setInterval', function() { // node.js. They do not stringify properly since they contain circular references. id = JSON.stringify((cancelId).data, function replaceTimer(key, value) { if (key == 'handleId' && typeof value == 'object') return value.constructor.name; + if (typeof value === 'function') return value.name; return value; }) as any as number; expect(wtfMock.log).toEqual([ diff --git a/test/common/setTimeout.spec.ts b/test/common/setTimeout.spec.ts index b609e7e46..e27fb14f6 100644 --- a/test/common/setTimeout.spec.ts +++ b/test/common/setTimeout.spec.ts @@ -39,6 +39,7 @@ describe('setTimeout', function() { // node.js. They do not stringify properly since they contain circular references. id = JSON.stringify((cancelId).data, function replaceTimer(key, value) { if (key == 'handleId' && typeof value == 'object') return value.constructor.name; + if (typeof value === 'function') return value.name; return value; }) as any as number; expect(wtfMock.log).toEqual([ diff --git a/test/common/task.spec.ts b/test/common/task.spec.ts index 3a544e92c..44bcb9eb5 100644 --- a/test/common/task.spec.ts +++ b/test/common/task.spec.ts @@ -955,92 +955,56 @@ describe('task lifecycle', () => { ]); })); - it('should not be able to reschedule task in notScheduled/running/canceling state', testFnWithLoggedTransitionTo( - () => { - Zone.current - .fork({ - name: - 'rescheduleNotScheduled' - }) - .run(() => { - const t = - Zone.current - .scheduleMacroTask( - 'testRescheduleZoneTask', - noop, - null, - noop, - noop); - Zone.current - .cancelTask( - t); - expect(() => { - t.cancelScheduleRequest(); - }) - .toThrow(Error( - `macroTask 'testRescheduleZoneTask': can not transition to 'notScheduled', expecting state 'scheduling', was 'notScheduled'.`)); - }); + it('should not be able to reschedule task in notScheduled / running / canceling state', + testFnWithLoggedTransitionTo(() => { + Zone.current.fork({name: 'rescheduleNotScheduled'}).run(() => { + const t = + Zone.current.scheduleMacroTask('testRescheduleZoneTask', noop, null, noop, noop); + Zone.current.cancelTask(t); + expect(() => { + t.cancelScheduleRequest(); + }) + .toThrow(Error( + `macroTask 'testRescheduleZoneTask': can not transition to ` + + `'notScheduled', expecting state 'scheduling', was 'notScheduled'.`)); + }); - Zone.current - .fork({ - name: - 'rescheduleRunning', - onInvokeTask: - (delegate, - currZone, - targetZone, - task, - applyThis, - applyArgs) => { - expect(() => { - task.cancelScheduleRequest(); - }) - .toThrow(Error( - `macroTask 'testRescheduleZoneTask': can not transition to 'notScheduled', expecting state 'scheduling', was 'running'.`)); - } - }) - .run(() => { - const t = - Zone.current - .scheduleMacroTask( - 'testRescheduleZoneTask', - noop, - null, - noop, - noop); - t.invoke(); - }); + Zone.current + .fork({ + name: 'rescheduleRunning', + onInvokeTask: (delegate, currZone, targetZone, task, applyThis, applyArgs) => { + expect(() => { + task.cancelScheduleRequest(); + }) + .toThrow(Error( + `macroTask 'testRescheduleZoneTask': can not transition to ` + + `'notScheduled', expecting state 'scheduling', was 'running'.`)); + } + }) + .run(() => { + const t = + Zone.current.scheduleMacroTask('testRescheduleZoneTask', noop, null, noop, noop); + t.invoke(); + }); - Zone.current - .fork({ - name: - 'rescheduleCanceling', - onCancelTask: - (delegate, - currZone, - targetZone, - task) => { - expect(() => { - task.cancelScheduleRequest(); - }) - .toThrow(Error( - `macroTask 'testRescheduleZoneTask': can not transition to 'notScheduled', expecting state 'scheduling', was 'canceling'.`)); - } - }) - .run(() => { - const t = - Zone.current - .scheduleMacroTask( - 'testRescheduleZoneTask', - noop, - null, - noop, - noop); - Zone.current - .cancelTask( - t); - }); - })); + Zone.current + .fork({ + name: 'rescheduleCanceling', + onCancelTask: (delegate, currZone, targetZone, task) => { + expect(() => { + task.cancelScheduleRequest(); + }) + .toThrow(Error( + `macroTask 'testRescheduleZoneTask': can not transition to ` + + `'notScheduled', expecting state 'scheduling', was 'canceling'.`)); + } + }) + .run(() => { + const t = + Zone.current.scheduleMacroTask('testRescheduleZoneTask', noop, null, noop, noop); + Zone.current.cancelTask(t); + }); + })); it('can not reschedule a task to a zone which is the descendants of the original zone', testFnWithLoggedTransitionTo(() => { diff --git a/test/main.ts b/test/main.ts index 68849f16e..aa4f8bd2b 100644 --- a/test/main.ts +++ b/test/main.ts @@ -16,7 +16,7 @@ __karma__.loaded = function() {}; (window as any).global = window; System.config({defaultJSExtensions: true}); -let browserPatchedPromise = null; +let browserPatchedPromise: any = null; if ((window as any)[(Zone as any).__symbol__('setTimeout')]) { browserPatchedPromise = Promise.resolve('browserPatched'); } else { diff --git a/test/zone-spec/long-stack-trace-zone.spec.ts b/test/zone-spec/long-stack-trace-zone.spec.ts index baaf2e5c2..0668bc273 100644 --- a/test/zone-spec/long-stack-trace-zone.spec.ts +++ b/test/zone-spec/long-stack-trace-zone.spec.ts @@ -34,7 +34,7 @@ describe('longStackTraceZone', function() { setTimeout(function() { setTimeout(function() { try { - expect(log[0].stack.split('Elapsed: ').length).toBe(3); + expect(log[0].stack.split('Elapsed').length).toBe(3); done(); } catch (e) { expect(e).toBe(null); @@ -82,7 +82,7 @@ describe('longStackTraceZone', function() { }); setTimeout(function() { try { - expect(log[0].stack.split('Elapsed: ').length).toBe(5); + expect(log[0].stack.split('Elapsed').length).toBe(5); done(); } catch (e) { expect(e).toBe(null); @@ -105,7 +105,7 @@ describe('longStackTraceZone', function() { promise.catch(function(error) { // should be able to get long stack trace const longStackFrames: string = longStackTraceZoneSpec.getLongStackTrace(error); - expect(longStackFrames.split('Elapsed: ').length).toBe(4); + expect(longStackFrames.split('Elapsed').length).toBe(4); done(); }); }, 0);