fix(ivy): errors not being logged to ErrorHandler (#28447)

Fixes Ivy not passing thrown errors along to the `ErrorHandler`.

**Note:** the failing test had to be reworked a little bit, because it has some assertions that depend on an error context being logged, however Ivy doesn't keep track of the error context.

This PR resolves FW-840.

PR Close #28447
This commit is contained in:
Kristiyan Kostadinov
2019-01-30 15:23:23 +01:00
committed by Matias Niemelä
parent a98d66078d
commit fc88a79b32
3 changed files with 137 additions and 79 deletions

View File

@ -450,84 +450,97 @@ function declareTestsUsingBootstrap() {
if (getDOM().supportsDOMEvents()) {
// This test needs a real DOM....
fixmeIvy('FW-840: Exceptions thrown in event handlers are not reported to ErrorHandler')
.it('should keep change detecting if there was an error', (done) => {
@Component({
selector: COMP_SELECTOR,
template:
'<button (click)="next()"></button><button (click)="nextAndThrow()"></button><button (dirClick)="nextAndThrow()"></button><span>Value:{{value}}</span><span>{{throwIfNeeded()}}</span>'
})
class ErrorComp {
value = 0;
thrownValue = 0;
next() { this.value++; }
nextAndThrow() {
this.value++;
this.throwIfNeeded();
}
throwIfNeeded() {
NgZone.assertInAngularZone();
if (this.thrownValue !== this.value) {
this.thrownValue = this.value;
throw new Error(`Error: ${this.value}`);
}
}
it('should keep change detecting if there was an error', (done) => {
@Component({
selector: COMP_SELECTOR,
template:
'<button (click)="next()"></button><button (click)="nextAndThrow()"></button><button (dirClick)="nextAndThrow()"></button><span>Value:{{value}}</span><span>{{throwIfNeeded()}}</span>'
})
class ErrorComp {
value = 0;
thrownValue = 0;
next() { this.value++; }
nextAndThrow() {
this.value++;
this.throwIfNeeded();
}
throwIfNeeded() {
NgZone.assertInAngularZone();
if (this.thrownValue !== this.value) {
this.thrownValue = this.value;
throw new Error(`Error: ${this.value}`);
}
}
}
@Directive({selector: '[dirClick]'})
class EventDir {
@Output()
dirClick = new EventEmitter();
@Directive({selector: '[dirClick]'})
class EventDir {
@Output()
dirClick = new EventEmitter();
@HostListener('click', ['$event'])
onClick(event: any) { this.dirClick.next(event); }
}
@HostListener('click', ['$event'])
onClick(event: any) { this.dirClick.next(event); }
}
@NgModule({
imports: [BrowserModule],
declarations: [ErrorComp, EventDir],
bootstrap: [ErrorComp],
providers: [{provide: ErrorHandler, useValue: errorHandler}],
})
class TestModule {
}
@NgModule({
imports: [BrowserModule],
declarations: [ErrorComp, EventDir],
bootstrap: [ErrorComp],
providers: [{provide: ErrorHandler, useValue: errorHandler}],
})
class TestModule {
}
platformBrowserDynamic().bootstrapModule(TestModule).then((ref) => {
NgZone.assertNotInAngularZone();
const appRef = ref.injector.get(ApplicationRef) as ApplicationRef;
const compRef = appRef.components[0] as ComponentRef<ErrorComp>;
const compEl = compRef.location.nativeElement;
const nextBtn = compEl.children[0];
const nextAndThrowBtn = compEl.children[1];
const nextAndThrowDirBtn = compEl.children[2];
platformBrowserDynamic().bootstrapModule(TestModule).then((ref) => {
NgZone.assertNotInAngularZone();
const appRef = ref.injector.get(ApplicationRef) as ApplicationRef;
const compRef = appRef.components[0] as ComponentRef<ErrorComp>;
const compEl = compRef.location.nativeElement;
const nextBtn = compEl.children[0];
const nextAndThrowBtn = compEl.children[1];
const nextAndThrowDirBtn = compEl.children[2];
nextBtn.click();
assertValueAndErrors(compEl, 1, 0);
nextBtn.click();
assertValueAndErrors(compEl, 2, 2);
// Note: the amount of events sent to the logger will differ between ViewEngine
// and Ivy, because Ivy doesn't attach an error context. This means that the amount
// of logged errors increases by 1 for Ivy and 2 for ViewEngine after each event.
const errorDelta = ivyEnabled ? 1 : 2;
let currentErrorIndex = 0;
nextAndThrowBtn.click();
assertValueAndErrors(compEl, 3, 4);
nextAndThrowBtn.click();
assertValueAndErrors(compEl, 4, 6);
nextBtn.click();
assertValueAndErrors(compEl, 1, currentErrorIndex);
currentErrorIndex += errorDelta;
nextBtn.click();
assertValueAndErrors(compEl, 2, currentErrorIndex);
currentErrorIndex += errorDelta;
nextAndThrowDirBtn.click();
assertValueAndErrors(compEl, 5, 8);
nextAndThrowDirBtn.click();
assertValueAndErrors(compEl, 6, 10);
nextAndThrowBtn.click();
assertValueAndErrors(compEl, 3, currentErrorIndex);
currentErrorIndex += errorDelta;
nextAndThrowBtn.click();
assertValueAndErrors(compEl, 4, currentErrorIndex);
currentErrorIndex += errorDelta;
// Assert that there were no more errors
expect(logger.errors.length).toBe(12);
done();
});
nextAndThrowDirBtn.click();
assertValueAndErrors(compEl, 5, currentErrorIndex);
currentErrorIndex += errorDelta;
nextAndThrowDirBtn.click();
assertValueAndErrors(compEl, 6, currentErrorIndex);
currentErrorIndex += errorDelta;
function assertValueAndErrors(compEl: any, value: number, errorIndex: number) {
expect(compEl).toHaveText(`Value:${value}`);
expect(logger.errors[errorIndex][0]).toBe('ERROR');
expect(logger.errors[errorIndex][1].message).toBe(`Error: ${value}`);
expect(logger.errors[errorIndex + 1][0]).toBe('ERROR CONTEXT');
}
});
// Assert that there were no more errors
expect(logger.errors.length).toBe(currentErrorIndex);
done();
});
function assertValueAndErrors(compEl: any, value: number, errorIndex: number) {
expect(compEl).toHaveText(`Value:${value}`);
expect(logger.errors[errorIndex][0]).toBe('ERROR');
expect(logger.errors[errorIndex][1].message).toBe(`Error: ${value}`);
// Ivy doesn't attach an error context.
!ivyEnabled && expect(logger.errors[errorIndex + 1][0]).toBe('ERROR CONTEXT');
}
});
}
});
}