diff --git a/modules/angular2/src/facade/async.dart b/modules/angular2/src/facade/async.dart index f30ac08902..60796ea4df 100644 --- a/modules/angular2/src/facade/async.dart +++ b/modules/angular2/src/facade/async.dart @@ -6,7 +6,9 @@ export 'dart:async' show Future, Stream, StreamController, StreamSubscription; class PromiseWrapper { static Future resolve(obj) => new Future.value(obj); - static Future reject(obj) => new Future.error(obj); + static Future reject(obj) => new Future.error( + obj, + obj is Error ? obj.stackTrace : null); static Future all(List promises) => Future.wait(promises); @@ -98,6 +100,10 @@ class _Completer { } void reject(v) { - c.completeError(v); + var stack = null; + if (v is Error) { + stack = v.stackTrace; + } + c.completeError(v, stack); } } diff --git a/modules/angular2/test/core/compiler/integration_dart_spec.dart b/modules/angular2/test/core/compiler/integration_dart_spec.dart index da60c5020e..4e7e0ee89b 100644 --- a/modules/angular2/test/core/compiler/integration_dart_spec.dart +++ b/modules/angular2/test/core/compiler/integration_dart_spec.dart @@ -6,6 +6,19 @@ import 'package:angular2/di.dart'; import 'package:angular2/src/test_lib/test_bed.dart'; import 'package:angular2/test_lib.dart'; +class MockException implements Error { var message; var stackTrace; } + +void functionThatThrows() { + try { throw new MockException(); } + catch(e, stack) { + // If we lose the stack trace the message will no longer match + // the first line in the stack + e.message = stack.toString().split('\n')[0]; + e.stackTrace = stack; + rethrow; + } +} + main() { describe('TypeLiteral', () { it('should publish via injectables', @@ -22,6 +35,21 @@ main() { }); })); }); + + describe('Error handling', () { + it('should preserve stack traces throws from components', + inject([TestBed, AsyncTestCompleter], (tb, async) { + tb.overrideView(Dummy, new View( + template: '', + directives: [ThrowingComponent] + )); + + tb.createView(Dummy).catchError((e, stack) { + expect(stack.toString().split('\n')[0]).toEqual(e.message); + async.done(); + }); + })); + }); } @Component(selector: 'dummy') @@ -43,3 +71,13 @@ class TypeLiteralComponent { TypeLiteralComponent(this.list); } + +@Component( + selector: 'throwing-component' +) +@View(template: '') +class ThrowingComponent { + ThrowingComponent() { + functionThatThrows(); + } +} diff --git a/modules/angular2/test/facade/async_dart_spec.dart b/modules/angular2/test/facade/async_dart_spec.dart new file mode 100644 index 0000000000..5266dc1277 --- /dev/null +++ b/modules/angular2/test/facade/async_dart_spec.dart @@ -0,0 +1,100 @@ +/// This file contains tests that make sense only in Dart +library angular2.test.facade.async_dart_spec; + +import 'package:angular2/test_lib.dart'; +import 'package:angular2/src/facade/async.dart'; + +class MockException implements Error { var message; var stackTrace; } + +void functionThatThrows() { + try { throw new MockException(); } + catch(e, stack) { + // If we lose the stack trace the message will no longer match + // the first line in the stack + e.message = stack.toString().split('\n')[0]; + e.stackTrace = stack; + rethrow; + } +} + +void functionThatThrowsNonError() { + throw 'this is an error'; +} + +void expectFunctionThatThrowsWithStackTrace( + Future future, AsyncTestCompleter async) { + PromiseWrapper.catchError(future, (err, StackTrace stack) { + expect(stack.toString().split('\n')[0]).toEqual(err.message); + async.done(); + }); +} + +void expectFunctionThatThrowsWithoutStackTrace(Future future, + AsyncTestCompleter async) { + PromiseWrapper.catchError(future, (err, StackTrace stack) { + expect(stack).toBe(null); + async.done(); + }); +} + +main() { + describe('Completer', () { + + it('should preserve error stack traces', + inject([AsyncTestCompleter], (async) { + var c = PromiseWrapper.completer(); + + expectFunctionThatThrowsWithStackTrace(c.promise, async); + + try { + functionThatThrows(); + } catch(e) { + c.reject(e); + } + })); + + // TODO: We might fix this one day; for now testing it to be explicit + it('CANNOT preserve error stack traces for non-Errors', + inject([AsyncTestCompleter], (async) { + var c = PromiseWrapper.completer(); + + expectFunctionThatThrowsWithoutStackTrace(c.promise, async); + + try { + functionThatThrowsNonError(); + } catch(e) { + c.reject(e); + } + })); + + }); + + describe('PromiseWrapper', () { + + describe('reject', () { + + it('should preserve error stack traces', + inject([AsyncTestCompleter], (async) { + try { + functionThatThrows(); + } catch(e) { + var rejectedFuture = PromiseWrapper.reject(e); + expectFunctionThatThrowsWithStackTrace(rejectedFuture, async); + } + })); + + // TODO: We might fix this one day; for now testing it to be explicit + it('CANNOT preserve stack traces for non-Errors', + inject([AsyncTestCompleter], (async) { + try { + functionThatThrowsNonError(); + } catch(e) { + var rejectedFuture = PromiseWrapper.reject(e); + expectFunctionThatThrowsWithoutStackTrace(rejectedFuture, async); + } + })); + + }); + + }); +}