Skip to content

Commit b13ceca

Browse files
committed
fix(dialog): multiple dialogs not opening with disabled animations
With SHA 36f708c, second dialogs can be only opened if the first dialog finished animating. Due to the recent update to Angular 4.3, the animation trigger callbacks are firing in a wrong order (done callback fires before start), which causes the _isAnimating property to be set to true while the animation already finished. @crisbeto Not too happy how the tests ended up. Fixes #6719
1 parent 01622b1 commit b13ceca

File tree

3 files changed

+89
-80
lines changed

3 files changed

+89
-80
lines changed

src/lib/datepicker/datepicker.spec.ts

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import {
77
dispatchMouseEvent,
88
} from '@angular/cdk/testing';
99
import {Component, ViewChild} from '@angular/core';
10-
import {async, ComponentFixture, inject, TestBed} from '@angular/core/testing';
10+
import {async, ComponentFixture, inject, TestBed, fakeAsync, flush} from '@angular/core/testing';
1111
import {FormControl, FormsModule, ReactiveFormsModule} from '@angular/forms';
1212
import {
1313
DEC,
@@ -166,7 +166,7 @@ describe('MatDatepicker', () => {
166166
.toBe(true, 'Expected default ESCAPE action to be prevented.');
167167
});
168168

169-
it('close should close dialog', () => {
169+
it('close should close dialog', fakeAsync(() => {
170170
testComponent.touch = true;
171171
fixture.detectChanges();
172172

@@ -177,13 +177,13 @@ describe('MatDatepicker', () => {
177177

178178
testComponent.datepicker.close();
179179
fixture.detectChanges();
180+
flush();
180181

181-
fixture.whenStable().then(() => {
182-
expect(document.querySelector('mat-dialog-container')).toBeNull();
183-
});
184-
});
182+
expect(document.querySelector('mat-dialog-container')).toBeNull();
183+
flush();
184+
}));
185185

186-
it('setting selected should update input and close calendar', () => {
186+
it('setting selected should update input and close calendar', fakeAsync(() => {
187187
testComponent.touch = true;
188188
fixture.detectChanges();
189189

@@ -196,12 +196,12 @@ describe('MatDatepicker', () => {
196196
let cells = document.querySelectorAll('.mat-calendar-body-cell');
197197
dispatchMouseEvent(cells[1], 'click');
198198
fixture.detectChanges();
199+
flush();
199200

200-
fixture.whenStable().then(() => {
201-
expect(document.querySelector('mat-dialog-container')).toBeNull();
202-
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
203-
});
204-
});
201+
expect(document.querySelector('mat-dialog-container')).toBeNull();
202+
expect(testComponent.datepickerInput.value).toEqual(new Date(2020, JAN, 2));
203+
flush();
204+
}));
205205

206206
it('clicking the currently selected date should close the calendar ' +
207207
'without firing selectedChanged', () => {

src/lib/dialog/dialog-container.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -181,8 +181,13 @@ export class MatDialogContainer extends BasePortalHost {
181181
this._restoreFocus();
182182
}
183183

184-
this._animationStateChanged.emit(event);
185-
this._isAnimating = false;
184+
// Note: as of Angular 4.3, the animations module seems to fire the `start` callback before
185+
// the end if animations are disabled. Make this call async to ensure that it still fires
186+
// at the appropriate time.
187+
Promise.resolve().then(() => {
188+
this._animationStateChanged.emit(event);
189+
this._isAnimating = false;
190+
});
186191
}
187192

188193
/** Callback, invoked when an animation on the host starts. */

src/lib/dialog/dialog.spec.ts

Lines changed: 70 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import {
66
inject,
77
TestBed,
88
tick,
9+
flush,
910
} from '@angular/core/testing';
1011
import {
1112
ChangeDetectionStrategy,
@@ -165,21 +166,21 @@ describe('MatDialog', () => {
165166
expect(dialogContainerElement.getAttribute('aria-describedby')).toBe('description-element');
166167
});
167168

168-
it('should close a dialog and get back a result', async(() => {
169+
it('should close a dialog and get back a result', fakeAsync(() => {
169170
let dialogRef = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
170171
let afterCloseCallback = jasmine.createSpy('afterClose callback');
171172

172173
dialogRef.afterClosed().subscribe(afterCloseCallback);
173174
dialogRef.close('Charmander');
174175
viewContainerFixture.detectChanges();
176+
flush();
175177

176-
viewContainerFixture.whenStable().then(() => {
177-
expect(afterCloseCallback).toHaveBeenCalledWith('Charmander');
178-
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
179-
});
178+
expect(afterCloseCallback).toHaveBeenCalledWith('Charmander');
179+
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
180+
flush();
180181
}));
181182

182-
it('should close a dialog and get back a result before it is closed', async(() => {
183+
it('should close a dialog and get back a result before it is closed', fakeAsync(() => {
183184
const dialogRef = dialog.open(PizzaMsg, {viewContainerRef: testViewContainerRef});
184185

185186
// beforeClose should emit before dialog container is destroyed
@@ -191,24 +192,24 @@ describe('MatDialog', () => {
191192
dialogRef.beforeClose().subscribe(beforeCloseHandler);
192193
dialogRef.close('Bulbasaurus');
193194
viewContainerFixture.detectChanges();
195+
flush();
194196

195-
viewContainerFixture.whenStable().then(() => {
196-
expect(beforeCloseHandler).toHaveBeenCalledWith('Bulbasaurus');
197-
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
198-
});
197+
expect(beforeCloseHandler).toHaveBeenCalledWith('Bulbasaurus');
198+
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
199+
flush();
199200
}));
200201

201-
it('should close a dialog via the escape key', async(() => {
202+
it('should close a dialog via the escape key', fakeAsync(() => {
202203
dialog.open(PizzaMsg, {
203204
viewContainerRef: testViewContainerRef
204205
});
205206

206207
dispatchKeyboardEvent(document, 'keydown', ESCAPE);
207208
viewContainerFixture.detectChanges();
209+
flush();
208210

209-
viewContainerFixture.whenStable().then(() => {
210-
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
211-
});
211+
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
212+
flush();
212213
}));
213214

214215
it('should close from a ViewContainerRef with OnPush change detection', fakeAsync(() => {
@@ -236,7 +237,7 @@ describe('MatDialog', () => {
236237
.toBe(0, 'Expected no open dialogs.');
237238
}));
238239

239-
it('should close when clicking on the overlay backdrop', async(() => {
240+
it('should close when clicking on the overlay backdrop', fakeAsync(() => {
240241
dialog.open(PizzaMsg, {
241242
viewContainerRef: testViewContainerRef
242243
});
@@ -247,13 +248,13 @@ describe('MatDialog', () => {
247248

248249
backdrop.click();
249250
viewContainerFixture.detectChanges();
251+
flush();
250252

251-
viewContainerFixture.whenStable().then(() => {
252-
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy();
253-
});
253+
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy();
254+
flush();
254255
}));
255256

256-
it('should emit the backdropClick stream when clicking on the overlay backdrop', async(() => {
257+
it('should emit the backdropClick stream when clicking on the overlay backdrop', fakeAsync(() => {
257258
const dialogRef = dialog.open(PizzaMsg, {
258259
viewContainerRef: testViewContainerRef
259260
});
@@ -269,12 +270,12 @@ describe('MatDialog', () => {
269270
expect(spy).toHaveBeenCalledTimes(1);
270271

271272
viewContainerFixture.detectChanges();
273+
flush();
272274

273-
viewContainerFixture.whenStable().then(() => {
274-
// Additional clicks after the dialog has closed should not be emitted
275-
backdrop.click();
276-
expect(spy).toHaveBeenCalledTimes(1);
277-
});
275+
// Additional clicks after the dialog has closed should not be emitted
276+
backdrop.click();
277+
expect(spy).toHaveBeenCalledTimes(1);
278+
flush();
278279
}));
279280

280281
it('should notify the observers if a dialog has been opened', () => {
@@ -285,7 +286,7 @@ describe('MatDialog', () => {
285286
});
286287
});
287288

288-
it('should notify the observers if all open dialogs have finished closing', async(() => {
289+
it('should notify the observers if all open dialogs have finished closing', fakeAsync(() => {
289290
const ref1 = dialog.open(PizzaMsg, { viewContainerRef: testViewContainerRef });
290291
const ref2 = dialog.open(ContentElementDialog, { viewContainerRef: testViewContainerRef });
291292
const spy = jasmine.createSpy('afterAllClosed spy');
@@ -294,14 +295,16 @@ describe('MatDialog', () => {
294295

295296
ref1.close();
296297
viewContainerFixture.detectChanges();
298+
flush();
297299

298-
viewContainerFixture.whenStable().then(() => {
299-
expect(spy).not.toHaveBeenCalled();
300+
expect(spy).not.toHaveBeenCalled();
300301

301-
ref2.close();
302-
viewContainerFixture.detectChanges();
303-
viewContainerFixture.whenStable().then(() => expect(spy).toHaveBeenCalled());
304-
});
302+
ref2.close();
303+
viewContainerFixture.detectChanges();
304+
flush();
305+
306+
expect(spy).toHaveBeenCalled();
307+
flush();
305308
}));
306309

307310
it('should emit the afterAllClosed stream on subscribe if there are no open dialogs', () => {
@@ -441,8 +444,8 @@ describe('MatDialog', () => {
441444

442445
expect(dialogRef.componentInstance.directionality.value).toBe('rtl');
443446
});
444-
445-
it('should close all of the dialogs', async(() => {
447+
448+
it('should close all of the dialogs', fakeAsync(() => {
446449
dialog.open(PizzaMsg);
447450
dialog.open(PizzaMsg);
448451
dialog.open(PizzaMsg);
@@ -451,10 +454,10 @@ describe('MatDialog', () => {
451454

452455
dialog.closeAll();
453456
viewContainerFixture.detectChanges();
457+
flush();
454458

455-
viewContainerFixture.whenStable().then(() => {
456-
expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0);
457-
});
459+
expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0);
460+
flush();
458461
}));
459462

460463
it('should set the proper animation states', () => {
@@ -469,32 +472,32 @@ describe('MatDialog', () => {
469472
expect(dialogContainer._state).toBe('exit');
470473
});
471474

472-
it('should close all dialogs when the user goes forwards/backwards in history', async(() => {
475+
it('should close all dialogs when the user goes forwards/backwards in history', fakeAsync(() => {
473476
dialog.open(PizzaMsg);
474477
dialog.open(PizzaMsg);
475478

476479
expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(2);
477480

478481
mockLocation.simulateUrlPop('');
479482
viewContainerFixture.detectChanges();
483+
flush();
480484

481-
viewContainerFixture.whenStable().then(() => {
482-
expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0);
483-
});
485+
expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0);
486+
flush();
484487
}));
485488

486-
it('should close all open dialogs when the location hash changes', async(() => {
489+
it('should close all open dialogs when the location hash changes', fakeAsync(() => {
487490
dialog.open(PizzaMsg);
488491
dialog.open(PizzaMsg);
489492

490493
expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(2);
491494

492495
mockLocation.simulateHashChange('');
493496
viewContainerFixture.detectChanges();
497+
flush();
494498

495-
viewContainerFixture.whenStable().then(() => {
496-
expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0);
497-
});
499+
expect(overlayContainerElement.querySelectorAll('mat-dialog-container').length).toBe(0);
500+
flush();
498501
}));
499502

500503
it('should have the componentInstance available in the afterClosed callback', fakeAsync(() => {
@@ -543,17 +546,17 @@ describe('MatDialog', () => {
543546
});
544547
});
545548

546-
it('should not keep a reference to the component after the dialog is closed', async(() => {
549+
it('should not keep a reference to the component after the dialog is closed', fakeAsync(() => {
547550
let dialogRef = dialog.open(PizzaMsg);
548551

549552
expect(dialogRef.componentInstance).toBeTruthy();
550553

551554
dialogRef.close();
552555
viewContainerFixture.detectChanges();
556+
flush();
553557

554-
viewContainerFixture.whenStable().then(() => {
555-
expect(dialogRef.componentInstance).toBeFalsy('Expected reference to have been cleared.');
556-
});
558+
expect(dialogRef.componentInstance).toBeFalsy('Expected reference to have been cleared.');
559+
flush();
557560
}));
558561

559562
it('should assign a unique id to each dialog', () => {
@@ -607,7 +610,7 @@ describe('MatDialog', () => {
607610
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeTruthy();
608611
});
609612

610-
it('should allow for the disableClose option to be updated while open', async(() => {
613+
it('should allow for the disableClose option to be updated while open', fakeAsync(() => {
611614
let dialogRef = dialog.open(PizzaMsg, {
612615
disableClose: true,
613616
viewContainerRef: testViewContainerRef
@@ -624,9 +627,10 @@ describe('MatDialog', () => {
624627
backdrop.click();
625628

626629
viewContainerFixture.detectChanges();
627-
viewContainerFixture.whenStable().then(() => {
628-
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy();
629-
});
630+
flush();
631+
632+
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeFalsy();
633+
flush();
630634
}));
631635
});
632636

@@ -887,7 +891,7 @@ describe('MatDialog with a parent MatDialog', () => {
887891
});
888892

889893
it('should close dialogs opened by a parent when calling closeAll on a child MatDialog',
890-
async(() => {
894+
fakeAsync(() => {
891895
parentDialog.open(PizzaMsg);
892896
fixture.detectChanges();
893897

@@ -896,15 +900,15 @@ describe('MatDialog with a parent MatDialog', () => {
896900

897901
childDialog.closeAll();
898902
fixture.detectChanges();
903+
flush();
899904

900-
fixture.whenStable().then(() => {
901-
expect(overlayContainerElement.textContent!.trim())
902-
.toBe('', 'Expected closeAll on child MatDialog to close dialog opened by parent');
903-
});
905+
expect(overlayContainerElement.textContent!.trim())
906+
.toBe('', 'Expected closeAll on child MatDialog to close dialog opened by parent');
907+
flush();
904908
}));
905909

906910
it('should close dialogs opened by a child when calling closeAll on a parent MatDialog',
907-
async(() => {
911+
fakeAsync(() => {
908912
childDialog.open(PizzaMsg);
909913
fixture.detectChanges();
910914

@@ -913,22 +917,22 @@ describe('MatDialog with a parent MatDialog', () => {
913917

914918
parentDialog.closeAll();
915919
fixture.detectChanges();
920+
flush();
916921

917-
fixture.whenStable().then(() => {
918-
expect(overlayContainerElement.textContent!.trim())
919-
.toBe('', 'Expected closeAll on parent MatDialog to close dialog opened by child');
920-
});
922+
expect(overlayContainerElement.textContent!.trim())
923+
.toBe('', 'Expected closeAll on parent MatDialog to close dialog opened by child');
924+
flush();
921925
}));
922926

923-
it('should close the top dialog via the escape key', async(() => {
927+
it('should close the top dialog via the escape key', fakeAsync(() => {
924928
childDialog.open(PizzaMsg);
925929

926930
dispatchKeyboardEvent(document, 'keydown', ESCAPE);
927931
fixture.detectChanges();
932+
flush();
928933

929-
fixture.whenStable().then(() => {
930-
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
931-
});
934+
expect(overlayContainerElement.querySelector('mat-dialog-container')).toBeNull();
935+
flush();
932936
}));
933937
});
934938

0 commit comments

Comments
 (0)