@@ -84,12 +84,15 @@ describe('InteractionsComponent', () => {
8484 return fixture . nativeElement . textContent ;
8585 }
8686
87- it ( 'refreshes the engagement list after a successful save, without an explicit fixture.detectChanges() call' , async ( ) => {
88- // autoDetectChanges mirrors production: Angular re-renders on its own whenever
89- // the zone observes the app has gone idle, exactly like a running browser tab.
90- fixture . autoDetectChanges ( true ) ;
91- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
87+ async function flush ( ) {
88+ await new Promise ( ( resolve ) => setTimeout ( resolve , 0 ) ) ;
89+ await fixture . whenStable ( ) ;
9290 fixture . detectChanges ( ) ;
91+ }
92+
93+ it ( 'refreshes the engagement list after a successful save' , async ( ) => {
94+ fixture . autoDetectChanges ( true ) ;
95+ await flush ( ) ;
9396
9497 expect ( listText ( ) ) . toContain ( 'Engagements (0)' ) ;
9598 expect ( findMock ) . toHaveBeenCalledTimes ( 1 ) ;
@@ -105,10 +108,8 @@ describe('InteractionsComponent', () => {
105108 { } as any , // new interaction, no id -> create path
106109 ) ;
107110
108- // Give the save promise chain, the resource.reload() change-detection cycle, and the
109- // follow-up refetch a few turns of the event loop, same as a real browser would provide.
110- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
111- fixture . detectChanges ( ) ;
111+ // Give the save promise chain, resource.reload(), and follow-up refetch time to resolve.
112+ await flush ( ) ;
112113
113114 expect ( findMock ) . toHaveBeenCalledTimes ( 2 ) ;
114115 expect ( listText ( ) ) . toContain ( 'Engagements (1)' ) ;
@@ -125,18 +126,16 @@ describe('InteractionsComponent', () => {
125126 . mockReturnValue ( asyncOf ( [ list [ 0 ] ] ) ) ; // after delete: only the first remains
126127
127128 fixture . autoDetectChanges ( true ) ;
128- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
129- fixture . detectChanges ( ) ;
129+ await flush ( ) ;
130130
131131 // Select the second engagement -> its detail is shown.
132132 component . onInteractionItemClicked ( component . data ( ) ! [ 1 ] , null ) ;
133- fixture . detectChanges ( ) ;
133+ await flush ( ) ;
134134 expect ( component . interactionDetailForm ( ) ! . interaction ( ) ! . id ) . toBe ( 20 ) ;
135135
136136 // Delete the selected (second) engagement.
137137 await component . deleteInteraction ( component . data ( ) ! [ 1 ] ) ;
138- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
139- fixture . detectChanges ( ) ;
138+ await flush ( ) ;
140139
141140 // Detail panel now shows the first remaining engagement, not the deleted one.
142141 expect ( component . selectedItem ( ) ?. id ) . toBe ( 10 ) ;
@@ -150,18 +149,16 @@ describe('InteractionsComponent', () => {
150149 . mockReturnValue ( asyncOf ( [ ] ) ) ; // after delete: empty
151150
152151 fixture . autoDetectChanges ( true ) ;
153- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
154- fixture . detectChanges ( ) ;
152+ await flush ( ) ;
155153
156154 component . onInteractionItemClicked ( component . data ( ) ! [ 0 ] , null ) ;
157- fixture . detectChanges ( ) ;
155+ await flush ( ) ;
158156 expect ( component . interactionDetailForm ( ) ! . interaction ( ) ! . id ) . toBe ( 10 ) ;
159157 // The detail form is rendered for the selected engagement (empty state is gone).
160158 expect ( listText ( ) ) . not . toContain ( 'No engagement selected' ) ;
161159
162160 await component . deleteInteraction ( component . data ( ) ! [ 0 ] ) ;
163- await new Promise ( ( resolve ) => setTimeout ( resolve , 50 ) ) ;
164- fixture . detectChanges ( ) ;
161+ await flush ( ) ;
165162
166163 // No engagements left -> selection cleared and the detail panel actually re-renders to its
167164 // empty state in the DOM (the regression: the panel used to stay showing the deleted item).
0 commit comments