feat(exception_handler): print originalException and originalStack for all exceptions
This commit is contained in:
parent
0a8b3816f7
commit
e744409cb9
|
@ -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<Type | Binding | List<any>> {
|
|||
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<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
|
||||
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.
|
||||
|
|
|
@ -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) ?
|
||||
(<any>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) ? (<any>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;
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<String, String> get attrToPropMap => const <String, String>{
|
||||
'innerHtml': 'innerHTML',
|
||||
|
|
|
@ -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); }
|
||||
|
|
|
@ -23,6 +23,9 @@ export class DomAdapter {
|
|||
invoke(el: Element, methodName: string, args: List<any>): 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
|
||||
|
|
|
@ -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',
|
||||
|
|
|
@ -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'); }
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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<string, any>): List<Type | Binding | List<any>> {
|
||||
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<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
|
||||
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.
|
||||
|
|
|
@ -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) ?
|
||||
(<any>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);
|
||||
}
|
||||
}
|
|
@ -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', () => {
|
||||
|
|
|
@ -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,9 +92,8 @@ export function main() {
|
|||
inject([AsyncTestCompleter], (async) => {
|
||||
var refPromise = bootstrap(HelloRootDirectiveIsNotCmp, testBindings);
|
||||
|
||||
PromiseWrapper.then(refPromise, null, (reason) => {
|
||||
expect(reason.message)
|
||||
.toContain(
|
||||
PromiseWrapper.then(refPromise, null, (exception) => {
|
||||
expect(exception).toContainError(
|
||||
`Could not load '${stringify(HelloRootDirectiveIsNotCmp)}' because it is not a component.`);
|
||||
async.done();
|
||||
return null;
|
||||
|
@ -95,7 +101,10 @@ export function main() {
|
|||
}));
|
||||
|
||||
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();
|
||||
|
|
|
@ -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();
|
||||
});
|
||||
}));
|
||||
|
|
|
@ -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!");
|
||||
});
|
||||
|
||||
describe("context", () => {
|
||||
it("should print context", () => {
|
||||
try {
|
||||
handler.call(new BaseException("message!", null, null, "context!"));
|
||||
} catch (e) {
|
||||
}
|
||||
expect(log.result()).toContain("context!");
|
||||
var e = ExceptionHandler.exceptionToString(
|
||||
new BaseException("message!", null, null, "context!"));
|
||||
expect(e).toContain("context!");
|
||||
});
|
||||
|
||||
it("should print nested context", () => {
|
||||
try {
|
||||
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", () => {
|
||||
try {
|
||||
handler.call(new _CustomException());
|
||||
} catch (e) {
|
||||
}
|
||||
expect(log.result()).not.toContain("context");
|
||||
var e = ExceptionHandler.exceptionToString(new _CustomException());
|
||||
expect(e).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");
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
|
@ -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();
|
||||
});
|
||||
});
|
||||
|
|
Loading…
Reference in New Issue