feat(core): provide an error context when an exception happens in an error handler
This commit is contained in:
parent
1d4502944c
commit
8543c347a8
|
@ -90,8 +90,9 @@ export class AbstractChangeDetector implements ChangeDetector {
|
||||||
|
|
||||||
throwError(proto: ProtoRecord, exception: any, stack: any): void {
|
throwError(proto: ProtoRecord, exception: any, stack: any): void {
|
||||||
var c = this.dispatcher.getDebugContext(proto.bindingRecord.elementIndex, proto.directiveIndex);
|
var c = this.dispatcher.getDebugContext(proto.bindingRecord.elementIndex, proto.directiveIndex);
|
||||||
var context = new _Context(c["element"], c["componentElement"], c["directive"], c["context"],
|
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.directive, c.context,
|
||||||
c["locals"], c["injector"], proto.expressionAsString);
|
c.locals, c.injector, proto.expressionAsString) :
|
||||||
|
null;
|
||||||
throw new ChangeDetectionError(proto, exception, stack, context);
|
throw new ChangeDetectionError(proto, exception, stack, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
|
@ -7,7 +7,8 @@ import {
|
||||||
BaseException,
|
BaseException,
|
||||||
assertionsEnabled,
|
assertionsEnabled,
|
||||||
print,
|
print,
|
||||||
stringify
|
stringify,
|
||||||
|
isDart
|
||||||
} from 'angular2/src/facade/lang';
|
} from 'angular2/src/facade/lang';
|
||||||
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
import {BrowserDomAdapter} from 'angular2/src/dom/browser_adapter';
|
||||||
import {DOM} from 'angular2/src/dom/dom_adapter';
|
import {DOM} from 'angular2/src/dom/dom_adapter';
|
||||||
|
@ -128,7 +129,7 @@ function _injectorBindings(appComponentType): List<Type | Binding | List<any>> {
|
||||||
DirectiveResolver,
|
DirectiveResolver,
|
||||||
Parser,
|
Parser,
|
||||||
Lexer,
|
Lexer,
|
||||||
bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM), []),
|
bind(ExceptionHandler).toFactory(() => new ExceptionHandler(DOM, isDart ? false : true), []),
|
||||||
bind(XHR).toValue(new XHRImpl()),
|
bind(XHR).toValue(new XHRImpl()),
|
||||||
ComponentUrlMapper,
|
ComponentUrlMapper,
|
||||||
UrlResolver,
|
UrlResolver,
|
||||||
|
@ -280,8 +281,8 @@ export function commonBootstrap(
|
||||||
appComponentType: /*Type*/ any,
|
appComponentType: /*Type*/ any,
|
||||||
componentInjectableBindings: List<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
|
componentInjectableBindings: List<Type | Binding | List<any>> = null): Promise<ApplicationRef> {
|
||||||
BrowserDomAdapter.makeCurrent();
|
BrowserDomAdapter.makeCurrent();
|
||||||
var bootstrapProcess: PromiseCompleter<any> = PromiseWrapper.completer();
|
var bootstrapProcess = PromiseWrapper.completer();
|
||||||
var zone = createNgZone(new ExceptionHandler(DOM));
|
var zone = createNgZone(new ExceptionHandler(DOM, isDart ? false : true));
|
||||||
zone.run(() => {
|
zone.run(() => {
|
||||||
// TODO(rado): prepopulate template cache, so applications with only
|
// TODO(rado): prepopulate template cache, so applications with only
|
||||||
// index.html and main.js are possible.
|
// index.html and main.js are possible.
|
||||||
|
@ -290,8 +291,8 @@ export function commonBootstrap(
|
||||||
var exceptionHandler = appInjector.get(ExceptionHandler);
|
var exceptionHandler = appInjector.get(ExceptionHandler);
|
||||||
zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s));
|
zone.overrideOnErrorHandler((e, s) => exceptionHandler.call(e, s));
|
||||||
|
|
||||||
var compRefToken: Promise<any> =
|
try {
|
||||||
PromiseWrapper.wrap(() => appInjector.get(appComponentRefPromiseToken));
|
var compRefToken: Promise<any> = appInjector.get(appComponentRefPromiseToken);
|
||||||
var tick = (componentRef) => {
|
var tick = (componentRef) => {
|
||||||
var appChangeDetector = internalView(componentRef.hostView).changeDetector;
|
var appChangeDetector = internalView(componentRef.hostView).changeDetector;
|
||||||
// retrieve life cycle: may have already been created if injected in root component
|
// retrieve life cycle: may have already been created if injected in root component
|
||||||
|
@ -301,8 +302,16 @@ export function commonBootstrap(
|
||||||
|
|
||||||
bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector));
|
bootstrapProcess.resolve(new ApplicationRef(componentRef, appComponentType, appInjector));
|
||||||
};
|
};
|
||||||
PromiseWrapper.then(compRefToken, tick,
|
|
||||||
(err, stackTrace) => bootstrapProcess.reject(err, stackTrace));
|
var tickResult = PromiseWrapper.then(compRefToken, tick);
|
||||||
|
|
||||||
|
PromiseWrapper.then(tickResult,
|
||||||
|
(_) => {}); // required for Dart to trigger the default error handler
|
||||||
|
PromiseWrapper.then(tickResult, null,
|
||||||
|
(err, stackTrace) => { bootstrapProcess.reject(err, stackTrace); });
|
||||||
|
} catch (e) {
|
||||||
|
bootstrapProcess.reject(e, e.stack);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
return bootstrapProcess.promise;
|
return bootstrapProcess.promise;
|
||||||
|
|
|
@ -502,7 +502,7 @@ export class ElementInjector extends TreeNode<ElementInjector> implements Depend
|
||||||
var p = this._preBuiltObjects;
|
var p = this._preBuiltObjects;
|
||||||
var index = p.elementRef.boundElementIndex - p.view.elementOffset;
|
var index = p.elementRef.boundElementIndex - p.view.elementOffset;
|
||||||
var c = this._preBuiltObjects.view.getDebugContext(index, null);
|
var c = this._preBuiltObjects.view.getDebugContext(index, null);
|
||||||
return new _Context(c["element"], c["componentElement"], c["injector"]);
|
return isPresent(c) ? new _Context(c.element, c.componentElement, c.injector) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
private _reattachInjectors(imperativelyCreatedInjector: Injector): void {
|
private _reattachInjectors(imperativelyCreatedInjector: Injector): void {
|
||||||
|
|
|
@ -212,7 +212,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||||
return isPresent(boundElementIndex) ? this.elementRefs[boundElementIndex] : null;
|
return isPresent(boundElementIndex) ? this.elementRefs[boundElementIndex] : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): StringMap<string, any> {
|
getDebugContext(elementIndex: number, directiveIndex: DirectiveIndex): DebugContext {
|
||||||
try {
|
try {
|
||||||
var offsettedIndex = this.elementOffset + elementIndex;
|
var offsettedIndex = this.elementOffset + elementIndex;
|
||||||
var hasRefForIndex = offsettedIndex < this.elementRefs.length;
|
var hasRefForIndex = offsettedIndex < this.elementRefs.length;
|
||||||
|
@ -226,18 +226,13 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||||
var directive = isPresent(directiveIndex) ? this.getDirectiveFor(directiveIndex) : null;
|
var directive = isPresent(directiveIndex) ? this.getDirectiveFor(directiveIndex) : null;
|
||||||
var injector = isPresent(ei) ? ei.getInjector() : null;
|
var injector = isPresent(ei) ? ei.getInjector() : null;
|
||||||
|
|
||||||
return {
|
return new DebugContext(element, componentElement, directive, this.context,
|
||||||
element: element,
|
_localsToStringMap(this.locals), injector);
|
||||||
componentElement: componentElement,
|
|
||||||
directive: directive,
|
|
||||||
context: this.context,
|
|
||||||
locals: _localsToStringMap(this.locals),
|
|
||||||
injector: injector
|
|
||||||
};
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
// TODO: vsavkin log the exception once we have a good way to log errors and warnings
|
// TODO: vsavkin log the exception once we have a good way to log errors and warnings
|
||||||
// if an error happens during getting the debug context, we return an empty map.
|
// if an error happens during getting the debug context, we return an empty map.
|
||||||
return {};
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -262,6 +257,7 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||||
|
|
||||||
// returns false if preventDefault must be applied to the DOM event
|
// returns false if preventDefault must be applied to the DOM event
|
||||||
dispatchEvent(boundElementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
dispatchEvent(boundElementIndex: number, eventName: string, locals: Map<string, any>): boolean {
|
||||||
|
try {
|
||||||
// Most of the time the event will be fired only when the view is in the live document.
|
// Most of the time the event will be fired only when the view is in the live document.
|
||||||
// However, in a rare circumstance the view might get dehydrated, in between the event
|
// However, in a rare circumstance the view might get dehydrated, in between the event
|
||||||
// queuing up and firing.
|
// queuing up and firing.
|
||||||
|
@ -285,6 +281,13 @@ export class AppView implements ChangeDispatcher, RenderEventDispatcher {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return allowDefaultBehavior;
|
return allowDefaultBehavior;
|
||||||
|
} catch (e) {
|
||||||
|
var c = this.getDebugContext(boundElementIndex - this.elementOffset, null);
|
||||||
|
var context = isPresent(c) ? new _Context(c.element, c.componentElement, c.context, c.locals,
|
||||||
|
c.injector) :
|
||||||
|
null;
|
||||||
|
throw new EventEvaluationError(eventName, e, e.stack, context);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -298,6 +301,29 @@ function _localsToStringMap(locals: Locals): StringMap<string, any> {
|
||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export class DebugContext {
|
||||||
|
constructor(public element: any, public componentElement: any, public directive: any,
|
||||||
|
public context: any, public locals: any, public injector: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Error context included when an event handler throws an exception.
|
||||||
|
*/
|
||||||
|
class _Context {
|
||||||
|
constructor(public element: any, public componentElement: any, public context: any,
|
||||||
|
public locals: any, public injector: any) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Wraps an exception thrown by an event handler.
|
||||||
|
*/
|
||||||
|
class EventEvaluationError extends BaseException {
|
||||||
|
constructor(eventName: string, originalException: any, originalStack: any, context: any) {
|
||||||
|
super(`Error during evaluation of "${eventName}"`, originalException, originalStack, context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -80,7 +80,7 @@ export class ExceptionHandler {
|
||||||
|
|
||||||
_longStackTrace(stackTrace: any): any {
|
_longStackTrace(stackTrace: any): any {
|
||||||
return isListLikeIterable(stackTrace) ? (<any>stackTrace).join("\n\n-----async gap-----\n") :
|
return isListLikeIterable(stackTrace) ? (<any>stackTrace).join("\n\n-----async gap-----\n") :
|
||||||
stackTrace;
|
stackTrace.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
_findContext(exception: any): any {
|
_findContext(exception: any): any {
|
||||||
|
|
|
@ -5,6 +5,8 @@ import 'dart:math' as math;
|
||||||
import 'dart:convert' as convert;
|
import 'dart:convert' as convert;
|
||||||
import 'dart:async' show Future;
|
import 'dart:async' show Future;
|
||||||
|
|
||||||
|
bool isDart = true;
|
||||||
|
|
||||||
String getTypeNameForDebugging(Type type) => type.toString();
|
String getTypeNameForDebugging(Type type) => type.toString();
|
||||||
|
|
||||||
class Math {
|
class Math {
|
||||||
|
|
|
@ -9,6 +9,8 @@ export function getTypeNameForDebugging(type: Type): string {
|
||||||
return type['name'];
|
return type['name'];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export var isDart = false;
|
||||||
|
|
||||||
export class BaseException extends Error {
|
export class BaseException extends Error {
|
||||||
stack;
|
stack;
|
||||||
constructor(public message?: string, private _originalException?, private _originalStack?,
|
constructor(public message?: string, private _originalException?, private _originalStack?,
|
||||||
|
|
|
@ -80,6 +80,13 @@ void tick([int millis = 0]) {
|
||||||
_fakeAsync.elapse(duration);
|
_fakeAsync.elapse(duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This is not needed in Dart. Because quiver correctly removes a timer when
|
||||||
|
* it throws an exception.
|
||||||
|
*/
|
||||||
|
void clearPendingTimers() {
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flush any pending microtasks.
|
* Flush any pending microtasks.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -41,9 +41,7 @@ export function fakeAsync(fn: Function): Function {
|
||||||
return function(...args) {
|
return function(...args) {
|
||||||
// TODO(tbosch): This class should already be part of the jasmine typings but it is not...
|
// TODO(tbosch): This class should already be part of the jasmine typings but it is not...
|
||||||
_scheduler = new (<any>jasmine).DelayedFunctionScheduler();
|
_scheduler = new (<any>jasmine).DelayedFunctionScheduler();
|
||||||
ListWrapper.clear(_microtasks);
|
clearPendingTimers();
|
||||||
ListWrapper.clear(_pendingPeriodicTimers);
|
|
||||||
ListWrapper.clear(_pendingTimers);
|
|
||||||
|
|
||||||
let res = fakeAsyncZone.run(() => {
|
let res = fakeAsyncZone.run(() => {
|
||||||
let res = fn(...args);
|
let res = fn(...args);
|
||||||
|
@ -67,6 +65,14 @@ export function fakeAsync(fn: Function): Function {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO we should fix tick to dequeue the failed timer instead of relying on clearPendingTimers
|
||||||
|
export function clearPendingTimers() {
|
||||||
|
ListWrapper.clear(_microtasks);
|
||||||
|
ListWrapper.clear(_pendingPeriodicTimers);
|
||||||
|
ListWrapper.clear(_pendingTimers);
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
|
* Simulates the asynchronous passage of time for the timers in the fakeAsync zone.
|
||||||
*
|
*
|
||||||
|
|
|
@ -32,6 +32,7 @@ export interface NgMatchers extends jasmine.Matchers {
|
||||||
|
|
||||||
export var expect: (actual: any) => NgMatchers = <any>_global.expect;
|
export var expect: (actual: any) => NgMatchers = <any>_global.expect;
|
||||||
|
|
||||||
|
// TODO vsavkin: remove it and use lang/isDart instead
|
||||||
export var IS_DARTIUM = false;
|
export var IS_DARTIUM = false;
|
||||||
|
|
||||||
export class AsyncTestCompleter {
|
export class AsyncTestCompleter {
|
||||||
|
|
|
@ -1043,9 +1043,7 @@ class TestDispatcher implements ChangeDispatcher {
|
||||||
|
|
||||||
notifyOnAllChangesDone() { this.onAllChangesDoneCalled = true; }
|
notifyOnAllChangesDone() { this.onAllChangesDoneCalled = true; }
|
||||||
|
|
||||||
getDebugContext(a, b) {
|
getDebugContext(a, b) { return null; }
|
||||||
return {}
|
|
||||||
}
|
|
||||||
|
|
||||||
_asString(value) { return (isBlank(value) ? 'null' : value.toString()); }
|
_asString(value) { return (isBlank(value) ? 'null' : value.toString()); }
|
||||||
}
|
}
|
||||||
|
|
|
@ -66,12 +66,14 @@ class HelloRootMissingTemplate {
|
||||||
class HelloRootDirectiveIsNotCmp {
|
class HelloRootDirectiveIsNotCmp {
|
||||||
}
|
}
|
||||||
|
|
||||||
class _NilLogger {
|
class _ArrayLogger {
|
||||||
log(s: any): void {}
|
res: any[] = [];
|
||||||
logGroup(s: any): void {}
|
log(s: any): void { this.res.push(s); }
|
||||||
|
logGroup(s: any): void { this.res.push(s); }
|
||||||
logGroupEnd(){};
|
logGroupEnd(){};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
export function main() {
|
export function main() {
|
||||||
var fakeDoc, el, el2, testBindings, lightDom;
|
var fakeDoc, el, el2, testBindings, lightDom;
|
||||||
|
|
||||||
|
@ -90,7 +92,7 @@ export function main() {
|
||||||
|
|
||||||
it('should throw if bootstrapped Directive is not a Component',
|
it('should throw if bootstrapped Directive is not a Component',
|
||||||
inject([AsyncTestCompleter], (async) => {
|
inject([AsyncTestCompleter], (async) => {
|
||||||
var refPromise = bootstrap(HelloRootDirectiveIsNotCmp, testBindings);
|
var refPromise = bootstrap(HelloRootDirectiveIsNotCmp, [testBindings]);
|
||||||
|
|
||||||
PromiseWrapper.then(refPromise, null, (exception) => {
|
PromiseWrapper.then(refPromise, null, (exception) => {
|
||||||
expect(exception).toContainError(
|
expect(exception).toContainError(
|
||||||
|
@ -101,10 +103,11 @@ export function main() {
|
||||||
}));
|
}));
|
||||||
|
|
||||||
it('should throw if no element is found', inject([AsyncTestCompleter], (async) => {
|
it('should throw if no element is found', inject([AsyncTestCompleter], (async) => {
|
||||||
// do not print errors to the console
|
var logger = new _ArrayLogger();
|
||||||
var e = new ExceptionHandler(new _NilLogger());
|
var exceptionHandler = new ExceptionHandler(logger, IS_DARTIUM ? false : true);
|
||||||
|
|
||||||
var refPromise = bootstrap(HelloRootCmp, [bind(ExceptionHandler).toValue(e)]);
|
var refPromise =
|
||||||
|
bootstrap(HelloRootCmp, [bind(ExceptionHandler).toValue(exceptionHandler)]);
|
||||||
PromiseWrapper.then(refPromise, null, (reason) => {
|
PromiseWrapper.then(refPromise, null, (reason) => {
|
||||||
expect(reason.message).toContain('The selector "hello-app" did not match any elements');
|
expect(reason.message).toContain('The selector "hello-app" did not match any elements');
|
||||||
async.done();
|
async.done();
|
||||||
|
@ -112,6 +115,23 @@ export function main() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (DOM.supportsDOMEvents()) {
|
||||||
|
it('should invoke the default exception handler when bootstrap fails',
|
||||||
|
inject([AsyncTestCompleter], (async) => {
|
||||||
|
var logger = new _ArrayLogger();
|
||||||
|
var exceptionHandler = new ExceptionHandler(logger, IS_DARTIUM ? false : true);
|
||||||
|
|
||||||
|
var refPromise =
|
||||||
|
bootstrap(HelloRootCmp, [bind(ExceptionHandler).toValue(exceptionHandler)]);
|
||||||
|
PromiseWrapper.then(refPromise, null, (reason) => {
|
||||||
|
expect(logger.res.join(""))
|
||||||
|
.toContain('The selector "hello-app" did not match any elements');
|
||||||
|
async.done();
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
it('should create an injector promise', () => {
|
it('should create an injector promise', () => {
|
||||||
var refPromise = bootstrap(HelloRootCmp, testBindings);
|
var refPromise = bootstrap(HelloRootCmp, testBindings);
|
||||||
expect(refPromise).not.toBe(null);
|
expect(refPromise).not.toBe(null);
|
||||||
|
|
|
@ -16,7 +16,9 @@ import {
|
||||||
containsRegexp,
|
containsRegexp,
|
||||||
stringifyElement,
|
stringifyElement,
|
||||||
TestComponentBuilder,
|
TestComponentBuilder,
|
||||||
fakeAsync
|
fakeAsync,
|
||||||
|
tick,
|
||||||
|
clearPendingTimers
|
||||||
} from 'angular2/test_lib';
|
} from 'angular2/test_lib';
|
||||||
|
|
||||||
|
|
||||||
|
@ -1200,7 +1202,7 @@ export function main() {
|
||||||
expect(c.injector).toBeAnInstanceOf(Injector);
|
expect(c.injector).toBeAnInstanceOf(Injector);
|
||||||
expect(c.expression).toContain("one.two.three");
|
expect(c.expression).toContain("one.two.three");
|
||||||
expect(c.context).toBe(rootTC.componentInstance);
|
expect(c.context).toBe(rootTC.componentInstance);
|
||||||
expect(c.locals["local"]).not.toBeNull();
|
expect(c.locals["local"]).toBeDefined();
|
||||||
}
|
}
|
||||||
|
|
||||||
async.done();
|
async.done();
|
||||||
|
@ -1226,6 +1228,38 @@ export function main() {
|
||||||
});
|
});
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
if (DOM.supportsDOMEvents()) { // this is required to use fakeAsync
|
||||||
|
it('should provide an error context when an error happens in an event handler',
|
||||||
|
inject([TestComponentBuilder], fakeAsync((tcb: TestComponentBuilder) => {
|
||||||
|
|
||||||
|
tcb = tcb.overrideView(MyComp, new viewAnn.View({
|
||||||
|
template: `<span emitter listener (event)="throwError()" #local></span>`,
|
||||||
|
directives: [DirectiveEmitingEvent, DirectiveListeningEvent]
|
||||||
|
}));
|
||||||
|
|
||||||
|
var rootTC;
|
||||||
|
tcb.createAsync(MyComp).then(root => { rootTC = root; });
|
||||||
|
tick();
|
||||||
|
|
||||||
|
var tc = rootTC.componentViewChildren[0];
|
||||||
|
tc.inject(DirectiveEmitingEvent).fireEvent("boom");
|
||||||
|
|
||||||
|
try {
|
||||||
|
tick();
|
||||||
|
throw "Should throw";
|
||||||
|
} catch (e) {
|
||||||
|
clearPendingTimers();
|
||||||
|
|
||||||
|
var c = e.context;
|
||||||
|
expect(DOM.nodeName(c.element).toUpperCase()).toEqual("SPAN");
|
||||||
|
expect(DOM.nodeName(c.componentElement).toUpperCase()).toEqual("DIV");
|
||||||
|
expect(c.injector).toBeAnInstanceOf(Injector);
|
||||||
|
expect(c.context).toBe(rootTC.componentInstance);
|
||||||
|
expect(c.locals["local"]).toBeDefined();
|
||||||
|
}
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
|
||||||
if (!IS_DARTIUM) {
|
if (!IS_DARTIUM) {
|
||||||
it('should report a meaningful error when a directive is undefined',
|
it('should report a meaningful error when a directive is undefined',
|
||||||
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder,
|
inject([TestComponentBuilder, AsyncTestCompleter], (tcb: TestComponentBuilder,
|
||||||
|
@ -1513,6 +1547,8 @@ class MyComp {
|
||||||
this.ctxNumProp = 0;
|
this.ctxNumProp = 0;
|
||||||
this.ctxBoolProp = false;
|
this.ctxBoolProp = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
throwError() { throw 'boom'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@Component({selector: 'child-cmp', properties: ['dirProp'], viewInjector: [MyService]})
|
@Component({selector: 'child-cmp', properties: ['dirProp'], viewInjector: [MyService]})
|
||||||
|
|
Loading…
Reference in New Issue