File tree Expand file tree Collapse file tree 3 files changed +46
-0
lines changed
Expand file tree Collapse file tree 3 files changed +46
-0
lines changed Original file line number Diff line number Diff line change @@ -21,7 +21,9 @@ import {
2121import {
2222 ComponentFixtureAutoDetect ,
2323 ComponentFixtureNoNgZone ,
24+ fakeAsync ,
2425 TestBed ,
26+ tick ,
2527 waitForAsync ,
2628 withModule ,
2729} from '@angular/core/testing' ;
@@ -372,6 +374,37 @@ describe('ComponentFixture', () => {
372374 expect ( ( ) => fixture . detectChanges ( ) ) . toThrow ( ) ;
373375 } ) ;
374376
377+ it ( 'should not duplicate errors when used with fake async' , fakeAsync ( ( ) => {
378+ @Component ( {
379+ template : '<button (click)="doThrow()">a</button>' ,
380+ } )
381+ class Throwing {
382+ doThrow ( ) {
383+ throw new Error ( 'thrown' ) ;
384+ }
385+ }
386+ TestBed . configureTestingModule ( {
387+ providers : [
388+ {
389+ provide : ErrorHandler ,
390+ useClass : class {
391+ handleError ( e : unknown ) {
392+ throw e ;
393+ }
394+ } ,
395+ } ,
396+ ] ,
397+ } ) ;
398+ const fix = TestBed . createComponent ( Throwing ) ;
399+ try {
400+ fix . nativeElement . querySelector ( 'button' ) . click ( ) ;
401+ tick ( ) ;
402+ fail ( 'should have thrown' ) ;
403+ } catch ( e ) {
404+ expect ( ( e as Error ) . message ) . toMatch ( 'thrown' ) ;
405+ }
406+ } ) ) ;
407+
375408 describe ( 'errors during ApplicationRef.tick' , ( ) => {
376409 @Component ( {
377410 template : '' ,
Original file line number Diff line number Diff line change @@ -122,6 +122,17 @@ export class ComponentFixture<T> {
122122 this . subscriptions . add (
123123 this . _ngZone . onError . subscribe ( {
124124 next : ( error : any ) => {
125+ // The rethrow here is to ensure that errors don't go unreported. Since `NgZone.onHandleError` returns `false`,
126+ // ZoneJS will not throw the error coming out of a task. Instead, the handling is defined by
127+ // the chain of parent delegates and whether they indicate the error is handled in some way (by returning `false`).
128+ // Unfortunately, 'onError' does not forward the information about whether the error was handled by a parent zone
129+ // so cannot know here whether throwing is appropriate. As a half-solution, we can check to see if we're inside
130+ // a fakeAsync context, which we know has its own error handling.
131+ // https://github.com/angular/angular/blob/db2f2d99c82aae52d8a0ae46616c6411d070b35e/packages/zone.js/lib/zone-spec/fake-async-test.ts#L783-L784
132+ // https://github.com/angular/angular/blob/db2f2d99c82aae52d8a0ae46616c6411d070b35e/packages/zone.js/lib/zone-spec/fake-async-test.ts#L473-L478
133+ if ( typeof Zone === 'undefined' || Zone . current . get ( 'FakeAsyncTestZoneSpec' ) ) {
134+ return ;
135+ }
125136 throw error ;
126137 } ,
127138 } ) ,
Original file line number Diff line number Diff line change @@ -780,6 +780,8 @@ class FakeAsyncTestZoneSpec implements ZoneSpec {
780780 targetZone : Zone ,
781781 error : any ,
782782 ) : boolean {
783+ // ComponentFixture has a special-case handling to detect FakeAsyncTestZoneSpec
784+ // and prevent rethrowing the error from the onError subscription since it's handled here.
783785 this . _lastError = error ;
784786 return false ; // Don't propagate error to parent zone.
785787 }
You can’t perform that action at this time.
0 commit comments