diff --git a/modules/angular2/src/core/application_common.ts b/modules/angular2/src/core/application_common.ts index 93982edec4..cba050c0cc 100644 --- a/modules/angular2/src/core/application_common.ts +++ b/modules/angular2/src/core/application_common.ts @@ -64,7 +64,6 @@ import { } from 'angular2/src/render/dom/dom_renderer'; import {DefaultDomCompiler} from 'angular2/src/render/dom/compiler/compiler'; import {internalView} from 'angular2/src/core/compiler/view_ref'; - import {appComponentRefPromiseToken, appComponentTypeToken} from './application_tokens'; var _rootInjector: Injector; @@ -129,7 +128,7 @@ function _injectorBindings(appComponentType): List> { DirectiveResolver, Parser, Lexer, - ExceptionHandler, + bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM)), bind(XHR).toValue(new XHRImpl()), ComponentUrlMapper, UrlResolver, @@ -282,8 +281,7 @@ export function commonBootstrap( componentInjectableBindings: List> = null): Promise { BrowserDomAdapter.makeCurrent(); var bootstrapProcess = PromiseWrapper.completer(); - - var zone = createNgZone(new ExceptionHandler()); + var zone = createNgZone(new ExceptionHandler(DOM)); zone.run(() => { // TODO(rado): prepopulate template cache, so applications with only // index.html and main.js are possible. diff --git a/modules/angular2/src/core/exception_handler.ts b/modules/angular2/src/core/exception_handler.ts index 33937a7d8a..cb07765da8 100644 --- a/modules/angular2/src/core/exception_handler.ts +++ b/modules/angular2/src/core/exception_handler.ts @@ -1,7 +1,13 @@ import {Injectable} from 'angular2/di'; -import {isPresent, print, BaseException} from 'angular2/src/facade/lang'; +import {isPresent, isBlank, print, BaseException} from 'angular2/src/facade/lang'; import {ListWrapper, isListLikeIterable} from 'angular2/src/facade/collection'; -import {DOM} from 'angular2/src/dom/dom_adapter'; + +class _ArrayLogger { + res: any[] = []; + log(s: any): void { this.res.push(s); } + logGroup(s: any): void { this.res.push(s); } + logGroupEnd(){}; +} /** * Provides a hook for centralized exception handling. @@ -26,31 +32,91 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; */ @Injectable() export class ExceptionHandler { - logError: Function = DOM.logError; + constructor(private logger: any, private rethrowException: boolean = true) {} - call(exception: Object, stackTrace: any = null, reason: string = null) { - var longStackTrace = isListLikeIterable(stackTrace) ? - (stackTrace).join("\n\n-----async gap-----\n") : - stackTrace; + static exceptionToString(exception: any, stackTrace: any = null, reason: string = null): string { + var l = new _ArrayLogger(); + var e = new ExceptionHandler(l, false); + e.call(exception, stackTrace, reason); + return l.res.join("\n"); + } - this.logError(`${exception}\n\n${longStackTrace}`); + call(exception: any, stackTrace: any = null, reason: string = null): void { + var originalException = this._findOriginalException(exception); + var originalStack = this._findOriginalStack(exception); + var context = this._findContext(exception); + + this.logger.logGroup(`EXCEPTION: ${exception}`); + + if (isPresent(stackTrace) && isBlank(originalStack)) { + this.logger.log("STACKTRACE:"); + this.logger.log(this._longStackTrace(stackTrace)) + } if (isPresent(reason)) { - this.logError(`Reason: ${reason}`); + this.logger.log(`REASON: ${reason}`); + } + + if (isPresent(originalException)) { + this.logger.log(`ORIGINAL EXCEPTION: ${originalException}`); + } + + if (isPresent(originalStack)) { + this.logger.log("ORIGINAL STACKTRACE:"); + this.logger.log(this._longStackTrace(originalStack)); } - var context = this._findContext(exception); if (isPresent(context)) { - this.logError("Error Context:"); - this.logError(context); + this.logger.log("ERROR CONTEXT:"); + this.logger.log(context); } - throw exception; + this.logger.logGroupEnd(); + + // We rethrow exceptions, so operations like 'bootstrap' will result in an error + // when an exception happens. If we do not rethrow, bootstrap will always succeed. + if (this.rethrowException) throw exception; + } + + _longStackTrace(stackTrace: any): any { + return isListLikeIterable(stackTrace) ? (stackTrace).join("\n\n-----async gap-----\n") : + stackTrace; } _findContext(exception: any): any { + try { + if (!(exception instanceof BaseException)) return null; + return isPresent(exception.context) ? exception.context : + this._findContext(exception.originalException); + } catch (e) { + // exception.context can throw an exception. if it happens, we ignore the context. + return null; + } + } + + _findOriginalException(exception: any): any { if (!(exception instanceof BaseException)) return null; - return isPresent(exception.context) ? exception.context : - this._findContext(exception.originalException); + + var e = exception.originalException; + while (e instanceof BaseException && isPresent(e.originalException)) { + e = e.originalException; + } + + return e; + } + + _findOriginalStack(exception: any): any { + if (!(exception instanceof BaseException)) return null; + + var e = exception; + var stack = exception.originalStack; + while (e instanceof BaseException && isPresent(e.originalException)) { + e = e.originalException; + if (e instanceof BaseException && isPresent(e.originalException)) { + stack = e.originalStack; + } + } + + return stack; } } diff --git a/modules/angular2/src/dom/browser_adapter.dart b/modules/angular2/src/dom/browser_adapter.dart index 0f4cc83298..b3855f37db 100644 --- a/modules/angular2/src/dom/browser_adapter.dart +++ b/modules/angular2/src/dom/browser_adapter.dart @@ -142,6 +142,18 @@ class BrowserDomAdapter extends GenericBrowserDomAdapter { window.console.error(error); } + log(error) { + window.console.log(error); + } + + logGroup(error) { + window.console.group(error); + } + + logGroupEnd() { + window.console.groupEnd(); + } + @override Map get attrToPropMap => const { 'innerHtml': 'innerHTML', diff --git a/modules/angular2/src/dom/browser_adapter.ts b/modules/angular2/src/dom/browser_adapter.ts index 68ef2f89b3..db285457cf 100644 --- a/modules/angular2/src/dom/browser_adapter.ts +++ b/modules/angular2/src/dom/browser_adapter.ts @@ -61,6 +61,22 @@ export class BrowserDomAdapter extends GenericBrowserDomAdapter { // TODO(tbosch): move this into a separate environment class once we have it logError(error) { window.console.error(error); } + log(error) { window.console.log(error); } + + logGroup(error) { + if (window.console.group) { + window.console.group(error); + } else { + window.console.log(error); + } + } + + logGroupEnd() { + if (window.console.groupEnd) { + window.console.groupEnd(); + } + } + get attrToPropMap(): any { return _attrToPropMap; } query(selector: string): any { return document.querySelector(selector); } diff --git a/modules/angular2/src/dom/dom_adapter.ts b/modules/angular2/src/dom/dom_adapter.ts index 4c9ee25bc6..3bba4d92ce 100644 --- a/modules/angular2/src/dom/dom_adapter.ts +++ b/modules/angular2/src/dom/dom_adapter.ts @@ -23,6 +23,9 @@ export class DomAdapter { invoke(el: Element, methodName: string, args: List): any { throw _abstract(); } logError(error) { throw _abstract(); } + log(error) { throw _abstract(); } + logGroup(error) { throw _abstract(); } + logGroupEnd() { throw _abstract(); } /** * Maps attribute names to their corresponding property names for cases diff --git a/modules/angular2/src/dom/html_adapter.dart b/modules/angular2/src/dom/html_adapter.dart index a2c576e031..d1dc7159f2 100644 --- a/modules/angular2/src/dom/html_adapter.dart +++ b/modules/angular2/src/dom/html_adapter.dart @@ -27,6 +27,11 @@ class Html5LibDomAdapter implements DomAdapter { stderr.writeln('${error}'); } + log(error) { stdout.writeln('${error}'); } + logGroup(error) { stdout.writeln('${error}'); } + logGroupEnd() { } + + @override final attrToPropMap = const { 'innerHtml': 'innerHTML', diff --git a/modules/angular2/src/dom/parse5_adapter.ts b/modules/angular2/src/dom/parse5_adapter.ts index 954c49b30c..4acfee7cc6 100644 --- a/modules/angular2/src/dom/parse5_adapter.ts +++ b/modules/angular2/src/dom/parse5_adapter.ts @@ -47,6 +47,12 @@ export class Parse5DomAdapter extends DomAdapter { logError(error) { console.error(error); } + log(error) { console.log(error); } + + logGroup(error) { console.log(error); } + + logGroupEnd() {} + get attrToPropMap() { return _attrToPropMap; } query(selector) { throw _notImplemented('query'); } diff --git a/modules/angular2/src/test_lib/test_injector.ts b/modules/angular2/src/test_lib/test_injector.ts index d185f01413..36347649cd 100644 --- a/modules/angular2/src/test_lib/test_injector.ts +++ b/modules/angular2/src/test_lib/test_injector.ts @@ -119,7 +119,7 @@ function _getAppBindings() { DirectiveResolver, Parser, Lexer, - ExceptionHandler, + bind(ExceptionHandler).toValue(new ExceptionHandler(DOM)), bind(LocationStrategy).toClass(MockLocationStrategy), bind(XHR).toClass(MockXHR), ComponentUrlMapper, diff --git a/modules/angular2/src/test_lib/test_lib.dart b/modules/angular2/src/test_lib/test_lib.dart index 9507926d4b..25ab62156d 100644 --- a/modules/angular2/src/test_lib/test_lib.dart +++ b/modules/angular2/src/test_lib/test_lib.dart @@ -22,6 +22,7 @@ import 'package:angular2/src/reflection/reflection_capabilities.dart'; import 'package:angular2/src/di/binding.dart' show bind; import 'package:angular2/src/di/injector.dart' show Injector; +import 'package:angular2/src/core/exception_handler.dart' show ExceptionHandler; import 'package:angular2/src/facade/collection.dart' show StringMapWrapper; import 'test_injector.dart'; @@ -77,13 +78,27 @@ Expect expect(actual, [matcher]) { const _u = const Object(); +expectErrorMessage(actual, expectedMessage) { + expect(ExceptionHandler.exceptionToString(actual)).toContain(expectedMessage); +} + +expectException(Function actual, expectedMessage) { + try { + actual(); + } catch (e, s) { + expectErrorMessage(e, expectedMessage); + } +} + class Expect extends gns.Expect { Expect(actual) : super(actual); NotExpect get not => new NotExpect(actual); void toEqual(expected) => toHaveSameProps(expected); + void toContainError(message) => expectErrorMessage(this.actual, message); void toThrowError([message = ""]) => toThrowWith(message: message); + void toThrowErrorWith(message) => expectException(this.actual, message); void toBePromise() => gns.guinness.matchers.toBeTrue(actual is Future); void toImplement(expected) => toBeA(expected); void toBeNaN() => gns.guinness.matchers.toBeTrue(double.NAN.compareTo(actual) == 0); diff --git a/modules/angular2/src/test_lib/test_lib.ts b/modules/angular2/src/test_lib/test_lib.ts index cb1c9934d9..e2417410a3 100644 --- a/modules/angular2/src/test_lib/test_lib.ts +++ b/modules/angular2/src/test_lib/test_lib.ts @@ -6,6 +6,7 @@ import {global} from 'angular2/src/facade/lang'; import {NgZoneZone} from 'angular2/src/core/zone/ng_zone'; import {bind} from 'angular2/di'; +import {ExceptionHandler} from 'angular2/src/core/exception_handler'; import {createTestInjector, FunctionWithParamTokens, inject} from './test_injector'; @@ -24,6 +25,8 @@ export interface NgMatchers extends jasmine.Matchers { toBeAnInstanceOf(expected: any): boolean; toHaveText(expected: any): boolean; toImplement(expected: any): boolean; + toContainError(expected: any): boolean; + toThrowErrorWith(expectedMessage: any): boolean; not: NgMatchers; } @@ -240,6 +243,38 @@ _global.beforeEach(function() { }; }, + toContainError: function() { + return { + compare: function(actual, expectedText) { + var errorMessage = ExceptionHandler.exceptionToString(actual); + return { + pass: errorMessage.indexOf(expectedText) > -1, + get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; } + }; + } + }; + }, + + toThrowErrorWith: function() { + return { + compare: function(actual, expectedText) { + try { + actual(); + return { + pass: false, + get message() { return "Was expected to throw, but did not throw"; } + }; + } catch (e) { + var errorMessage = ExceptionHandler.exceptionToString(e); + return { + pass: errorMessage.indexOf(expectedText) > -1, + get message() { return 'Expected ' + errorMessage + ' to contain ' + expectedText; } + }; + } + } + }; + }, + toImplement: function() { return { compare: function(actualObject, expectedInterface) { diff --git a/modules/angular2/src/web-workers/ui/impl.ts b/modules/angular2/src/web-workers/ui/impl.ts index 1e3c04b8d4..f0ef6c1da3 100644 --- a/modules/angular2/src/web-workers/ui/impl.ts +++ b/modules/angular2/src/web-workers/ui/impl.ts @@ -31,6 +31,7 @@ import {AnchorBasedAppRootUrl} from 'angular2/src/services/anchor_based_app_root import {ExceptionHandler} from 'angular2/src/core/exception_handler'; import {Injectable} from 'angular2/di'; import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; +import {DOM} from 'angular2/src/dom/dom_adapter'; /** * Creates a zone, sets up the DI bindings @@ -38,7 +39,7 @@ import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter'; */ export function bootstrapUICommon(bus: MessageBus) { BrowserDomAdapter.makeCurrent(); - var zone = createNgZone(new ExceptionHandler()); + var zone = createNgZone(new ExceptionHandler(DOM)); zone.run(() => { var injector = createInjector(zone); var webWorkerMain = injector.get(WebWorkerMain); diff --git a/modules/angular2/src/web-workers/worker/application_common.ts b/modules/angular2/src/web-workers/worker/application_common.ts index c5d86e2830..e912db0f2f 100644 --- a/modules/angular2/src/web-workers/worker/application_common.ts +++ b/modules/angular2/src/web-workers/worker/application_common.ts @@ -62,13 +62,18 @@ import {RenderProtoViewRefStore} from 'angular2/src/web-workers/shared/render_pr import { RenderViewWithFragmentsStore } from 'angular2/src/web-workers/shared/render_view_with_fragments_store'; -import {WorkerExceptionHandler} from 'angular2/src/web-workers/worker/exception_handler'; var _rootInjector: Injector; // Contains everything that is safe to share between applications. var _rootBindings = [bind(Reflector).toValue(reflector)]; +class PrintLogger { + log = print; + logGroup = print; + logGroupEnd() {} +} + function _injectorBindings(appComponentType, bus: WorkerMessageBus, initData: StringMap): List> { var bestChangeDetection: Type = DynamicChangeDetection; @@ -118,8 +123,7 @@ function _injectorBindings(appComponentType, bus: WorkerMessageBus, DirectiveResolver, Parser, Lexer, - WorkerExceptionHandler, - bind(ExceptionHandler).toAlias(WorkerExceptionHandler), + bind(ExceptionHandler).toFactory(() => {new ExceptionHandler(new PrintLogger())}), bind(XHR).toValue(new XHRImpl()), ComponentUrlMapper, UrlResolver, @@ -135,7 +139,7 @@ export function bootstrapWebworkerCommon( componentInjectableBindings: List> = null): Promise { var bootstrapProcess = PromiseWrapper.completer(); - var zone = createNgZone(new WorkerExceptionHandler()); + var zone = createNgZone(new ExceptionHandler(new PrintLogger())); zone.run(() => { // TODO(rado): prepopulate template cache, so applications with only // index.html and main.js are possible. diff --git a/modules/angular2/src/web-workers/worker/exception_handler.ts b/modules/angular2/src/web-workers/worker/exception_handler.ts deleted file mode 100644 index 32e09c09a6..0000000000 --- a/modules/angular2/src/web-workers/worker/exception_handler.ts +++ /dev/null @@ -1,35 +0,0 @@ -import {isPresent, print, BaseException} from 'angular2/src/facade/lang'; -import {ListWrapper, isListLikeIterable} from 'angular2/src/facade/collection'; -import {ExceptionHandler} from 'angular2/src/core/exception_handler'; -import {Injectable} from 'angular2/di'; - -@Injectable() -export class WorkerExceptionHandler implements ExceptionHandler { - logError: Function = print; - - call(exception: Object, stackTrace: any = null, reason: string = null) { - var longStackTrace = isListLikeIterable(stackTrace) ? - (stackTrace).join("\n\n-----async gap-----\n") : - stackTrace; - - this.logError(`${exception}\n\n${longStackTrace}`); - - if (isPresent(reason)) { - this.logError(`Reason: ${reason}`); - } - - var context = this._findContext(exception); - if (isPresent(context)) { - this.logError("Error Context:"); - this.logError(context); - } - - throw exception; - } - - _findContext(exception: any): any { - if (!(exception instanceof BaseException)) return null; - return isPresent(exception.context) ? exception.context : - this._findContext(exception.originalException); - } -} diff --git a/modules/angular2/test/change_detection/pipes/pipes_spec.ts b/modules/angular2/test/change_detection/pipes/pipes_spec.ts index 27fbc0d070..828b7d2a9c 100644 --- a/modules/angular2/test/change_detection/pipes/pipes_spec.ts +++ b/modules/angular2/test/change_detection/pipes/pipes_spec.ts @@ -132,7 +132,7 @@ export function main() { Injector.resolveAndCreate([Pipes.extend({'async': [secondPipeFactory]})]); expect(() => injector.get(Pipes)) - .toThrowError(/Cannot extend Pipes without a parent injector/g); + .toThrowErrorWith("Cannot extend Pipes without a parent injector"); }); it('should extend di-inherited pipes', () => { diff --git a/modules/angular2/test/core/application_spec.ts b/modules/angular2/test/core/application_spec.ts index 76a60d544f..f1b80b0496 100644 --- a/modules/angular2/test/core/application_spec.ts +++ b/modules/angular2/test/core/application_spec.ts @@ -18,6 +18,7 @@ import {DOM} from 'angular2/src/dom/dom_adapter'; import {PromiseWrapper} from 'angular2/src/facade/async'; import {bind, Inject, Injector} from 'angular2/di'; import {LifeCycle} from 'angular2/src/core/life_cycle/life_cycle'; +import {ExceptionHandler} from 'angular2/src/core/exception_handler'; import {Testability, TestabilityRegistry} from 'angular2/src/core/testability/testability'; import {DOCUMENT_TOKEN} from 'angular2/src/render/dom/dom_renderer'; @@ -65,6 +66,12 @@ class HelloRootMissingTemplate { class HelloRootDirectiveIsNotCmp { } +class _NilLogger { + log(s: any): void {} + logGroup(s: any): void {} + logGroupEnd(){}; +} + export function main() { var fakeDoc, el, el2, testBindings, lightDom; @@ -85,17 +92,19 @@ export function main() { inject([AsyncTestCompleter], (async) => { var refPromise = bootstrap(HelloRootDirectiveIsNotCmp, testBindings); - PromiseWrapper.then(refPromise, null, (reason) => { - expect(reason.message) - .toContain( - `Could not load '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`); + PromiseWrapper.then(refPromise, null, (exception) => { + expect(exception).toContainError( + `Could not load '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`); async.done(); return null; }); })); it('should throw if no element is found', inject([AsyncTestCompleter], (async) => { - var refPromise = bootstrap(HelloRootCmp, []); + // do not print errors to the console + var e = new ExceptionHandler(new _NilLogger()); + + var refPromise = bootstrap(HelloRootCmp, [bind(ExceptionHandler).toValue(e)]); PromiseWrapper.then(refPromise, null, (reason) => { expect(reason.message).toContain('The selector "hello-app" did not match any elements'); async.done(); diff --git a/modules/angular2/test/core/compiler/integration_dart_spec.dart b/modules/angular2/test/core/compiler/integration_dart_spec.dart index 04b52ecdef..6a37652ab0 100644 --- a/modules/angular2/test/core/compiler/integration_dart_spec.dart +++ b/modules/angular2/test/core/compiler/integration_dart_spec.dart @@ -67,8 +67,8 @@ main() { directives: [ThrowingComponent])) .createAsync(Dummy).catchError((e, stack) { - expect(e.message).toContain("MockException"); - expect(e.message).toContain("functionThatThrows"); + expect(e).toContainError("MockException"); + expect(e).toContainError("functionThatThrows"); async.done(); }); })); @@ -82,8 +82,8 @@ main() { directives: [ThrowingComponent2])) .createAsync(Dummy).catchError((e, stack) { - expect(e.message).toContain("NonError"); - expect(e.message).toContain("functionThatThrows"); + expect(e).toContainError("NonError"); + expect(e).toContainError("functionThatThrows"); async.done(); }); })); diff --git a/modules/angular2/test/core/exception_handler_spec.ts b/modules/angular2/test/core/exception_handler_spec.ts index 1985f0320d..75c77b1af1 100644 --- a/modules/angular2/test/core/exception_handler_spec.ts +++ b/modules/angular2/test/core/exception_handler_spec.ts @@ -17,74 +17,78 @@ import {ExceptionHandler} from 'angular2/src/core/exception_handler'; class _CustomException { context = "some context"; + toString(): string { return "custom"; } } export function main() { describe('ExceptionHandler', () => { - var log, handler; - - beforeEach(() => { - log = new Log(); - handler = new ExceptionHandler(); - handler.logError = (e) => log.add(e); - }); - it("should output exception", () => { - try { - handler.call(new BaseException("message!")); - } catch (e) { - } - expect(log.result()).toContain("message!"); + var e = ExceptionHandler.exceptionToString(new BaseException("message!")); + expect(e).toContain("message!"); }); it("should output stackTrace", () => { - try { - handler.call(new BaseException("message!"), "stack!"); - } catch (e) { - } - expect(log.result()).toContain("stack!"); + var e = ExceptionHandler.exceptionToString(new BaseException("message!"), "stack!"); + expect(e).toContain("stack!"); }); it("should join a long stackTrace", () => { - try { - handler.call(new BaseException("message!"), ["stack1", "stack2"]); - } catch (e) { - } - expect(log.result()).toContain("stack1"); - expect(log.result()).toContain("stack2"); + var e = + ExceptionHandler.exceptionToString(new BaseException("message!"), ["stack1", "stack2"]); + expect(e).toContain("stack1"); + expect(e).toContain("stack2"); }); it("should output reason when present", () => { - try { - handler.call(new BaseException("message!"), null, "reason!"); - } catch (e) { - } - expect(log.result()).toContain("reason!"); + var e = ExceptionHandler.exceptionToString(new BaseException("message!"), null, "reason!"); + expect(e).toContain("reason!"); }); - it("should print context", () => { - try { - handler.call(new BaseException("message!", null, null, "context!")); - } catch (e) { - } - expect(log.result()).toContain("context!"); - }); + describe("context", () => { + it("should print context", () => { + var e = ExceptionHandler.exceptionToString( + new BaseException("message!", null, null, "context!")); + expect(e).toContain("context!"); + }); - it("should print nested context", () => { - try { + it("should print nested context", () => { var original = new BaseException("message!", null, null, "context!"); - handler.call(new BaseException("message", original)); - } catch (e) { - } - expect(log.result()).toContain("context!"); + var e = ExceptionHandler.exceptionToString(new BaseException("message", original)); + expect(e).toContain("context!"); + }); + + it("should not print context when the passed-in exception is not a BaseException", () => { + var e = ExceptionHandler.exceptionToString(new _CustomException()); + expect(e).not.toContain("context"); + }); }); - it("should not print context when the passed-in exception is not a BaseException", () => { - try { - handler.call(new _CustomException()); - } catch (e) { - } - expect(log.result()).not.toContain("context"); + describe('original exception', () => { + it("should print original exception message if available (original is BaseException)", () => { + var realOriginal = new BaseException("inner"); + var original = new BaseException("wrapped", realOriginal); + var e = ExceptionHandler.exceptionToString(new BaseException("wrappedwrapped", original)); + expect(e).toContain("inner"); + }); + + it("should print original exception message if available (original is not BaseException)", + () => { + var realOriginal = new _CustomException(); + var original = new BaseException("wrapped", realOriginal); + var e = + ExceptionHandler.exceptionToString(new BaseException("wrappedwrapped", original)); + expect(e).toContain("custom"); + }); + }); + + describe('original stack', () => { + it("should print original stack if available", () => { + var realOriginal = new BaseException("inner"); + var original = new BaseException("wrapped", realOriginal, "originalStack"); + var e = ExceptionHandler.exceptionToString( + new BaseException("wrappedwrapped", original, "wrappedStack")); + expect(e).toContain("originalStack"); + }); }); }); -} +} \ No newline at end of file diff --git a/modules/angular2/test/router/router_integration_spec.ts b/modules/angular2/test/router/router_integration_spec.ts index 30fb426697..fa6c860996 100644 --- a/modules/angular2/test/router/router_integration_spec.ts +++ b/modules/angular2/test/router/router_integration_spec.ts @@ -74,7 +74,7 @@ export function main() { var router = rootTC.componentInstance.router; PromiseWrapper.catchError(router.navigate('/cause-error'), (error) => { expect(rootTC.nativeElement).toHaveText('outer { oh no }'); - expect(error.message).toContain('oops!'); + expect(error).toContainError('oops!'); async.done(); }); });