Skip to content

Commit e9bb973

Browse files
committed
refactor(admin): replace arbitrary timeouts with deterministic flush helper in interactions spec
1 parent a8bf5e4 commit e9bb973

1 file changed

Lines changed: 16 additions & 19 deletions

File tree

admin/src/app/foms/interactions/interactions.component.spec.ts

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)